1#![allow(missing_docs)]
3
4use core::borrow::Borrow;
5
6#[cfg(feature = "alloc")]
7use alloc::boxed::Box;
8use const_num_traits::PrimBits;
9#[cfg(not(feature = "modmath"))]
10use const_num_traits::PrimInt;
11use const_num_traits::{FromBytes as NumFromBytes, ToBytes as NumToBytes, Zero};
12#[cfg(feature = "alloc")]
13use crypto_bigint::{
14 modular::{BoxedMontyForm, BoxedMontyParams},
15 BoxedUint, Resize as CryptoResize,
16};
17#[cfg(feature = "alloc")]
18use crypto_bigint::{NonZero as CryptoNonZero, Odd as CryptoOdd};
19use zeroize::Zeroize;
20
21use crate::errors::{Error, Result};
22
23pub trait NumBytes: Borrow<[u8]> + AsRef<[u8]> {}
24
25impl<T> NumBytes for T where T: Borrow<[u8]> + AsRef<[u8]> {}
26
27#[repr(transparent)]
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct NonZero<T>(T);
30
31#[repr(transparent)]
32#[derive(Clone, Debug, Eq, PartialEq)]
33pub struct Odd<T>(T);
34
35pub trait IntegerResize: Sized {
36 type Output;
37
38 fn resize_unchecked(self, at_least_bits_precision: u32) -> Self::Output;
39 fn try_resize(self, at_least_bits_precision: u32) -> Option<Self::Output>;
40}
41
42pub trait FixedWidthUnsignedInt: Zeroize + Clone + Copy {
43 type Bytes: NumBytes + Default + AsMut<[u8]>;
44
45 fn leading_zeros(&self) -> u32;
46 fn to_be_bytes(&self) -> Self::Bytes;
47 fn try_from_be_bytes_vartime(bytes: &[u8]) -> Result<Self>;
48 fn bits_precision(&self) -> u32;
49}
50
51#[cfg(feature = "modmath")]
52impl<T> FixedWidthUnsignedInt for T
53where
54 T: Zeroize + Clone + Copy + PrimBits + Zero + NumToBytes + NumFromBytes,
55 T: NumToBytes<Bytes = <T as NumFromBytes>::Bytes>,
56 <T as NumToBytes>::Bytes: NumBytes + Default + AsMut<[u8]>,
57{
58 type Bytes = <T as NumToBytes>::Bytes;
59
60 fn leading_zeros(&self) -> u32 {
61 PrimBits::leading_zeros(*self)
62 }
63
64 fn to_be_bytes(&self) -> Self::Bytes {
65 NumToBytes::to_be_bytes(*self)
66 }
67
68 fn try_from_be_bytes_vartime(bytes: &[u8]) -> Result<Self> {
69 let mut repr = <T as NumFromBytes>::Bytes::default();
70 let out = repr.as_mut();
71 let out_len = out.len();
72 if bytes.len() > out_len {
73 return Err(Error::InvalidArguments);
74 }
75 let dst = out
79 .get_mut(out_len - bytes.len()..)
80 .ok_or(Error::InvalidArguments)?;
81 for (d, s) in dst.iter_mut().zip(bytes.iter()) {
82 *d = *s;
83 }
84 Ok(NumFromBytes::from_be_bytes(&repr))
85 }
86
87 fn bits_precision(&self) -> u32 {
88 PrimBits::count_zeros(<T as Zero>::zero())
89 }
90}
91
92#[cfg(not(feature = "modmath"))]
93impl<T> FixedWidthUnsignedInt for T
94where
95 T: Zeroize + Clone + Copy + PrimInt + NumToBytes + NumFromBytes,
96 T: NumToBytes<Bytes = <T as NumFromBytes>::Bytes>,
97 <T as NumToBytes>::Bytes: NumBytes + Default + AsMut<[u8]>,
98{
99 type Bytes = <T as NumToBytes>::Bytes;
100
101 fn leading_zeros(&self) -> u32 {
102 PrimBits::leading_zeros(*self)
103 }
104
105 fn to_be_bytes(&self) -> Self::Bytes {
106 NumToBytes::to_be_bytes(*self)
107 }
108
109 fn try_from_be_bytes_vartime(bytes: &[u8]) -> Result<Self> {
110 let mut repr = <T as NumFromBytes>::Bytes::default();
111 let out = repr.as_mut();
112 let out_len = out.len();
113 if bytes.len() > out_len {
114 return Err(Error::InvalidArguments);
115 }
116 let dst = out
120 .get_mut(out_len - bytes.len()..)
121 .ok_or(Error::InvalidArguments)?;
122 for (d, s) in dst.iter_mut().zip(bytes.iter()) {
123 *d = *s;
124 }
125 Ok(NumFromBytes::from_be_bytes(&repr))
126 }
127
128 fn bits_precision(&self) -> u32 {
129 <T as Zero>::zero().count_zeros()
130 }
131}
132
133#[cfg(not(feature = "alloc"))]
134impl<T> IntegerResize for T
135where
136 T: FixedWidthUnsignedInt,
137{
138 type Output = Self;
139
140 fn resize_unchecked(self, _at_least_bits_precision: u32) -> Self::Output {
141 self
142 }
143
144 fn try_resize(self, at_least_bits_precision: u32) -> Option<Self::Output> {
145 let value_bits = self.bits_precision() - self.leading_zeros();
151 if value_bits <= at_least_bits_precision {
152 Some(self)
153 } else {
154 None
155 }
156 }
157}
158
159#[cfg(not(feature = "alloc"))]
160impl<T> UnsignedModularInt for T
161where
162 T: FixedWidthUnsignedInt + PartialOrd,
163{
164 type Bytes = <T as FixedWidthUnsignedInt>::Bytes;
165
166 fn leading_zeros(&self) -> u32 {
167 FixedWidthUnsignedInt::leading_zeros(self)
168 }
169
170 fn to_be_bytes(&self) -> Self::Bytes {
171 FixedWidthUnsignedInt::to_be_bytes(self)
172 }
173
174 fn as_nz_ref(&self) -> NonZero<Self> {
175 NonZero::new(*self).expect("value is non-zero")
176 }
177
178 fn bits(&self) -> u32 {
179 self.bits_precision() - self.leading_zeros()
180 }
181
182 fn bits_precision(&self) -> u32 {
183 FixedWidthUnsignedInt::bits_precision(self)
184 }
185
186 #[cfg(feature = "alloc")]
187 fn to_be_bytes_trimmed_vartime(&self) -> Box<[u8]> {
188 unreachable!("alloc-gated")
189 }
190}
191
192#[cfg(not(feature = "alloc"))]
193impl<T> TryFromBeBytes for T
194where
195 T: FixedWidthUnsignedInt,
196{
197 fn try_from_be_bytes_vartime(bytes: &[u8]) -> Result<Self> {
198 FixedWidthUnsignedInt::try_from_be_bytes_vartime(bytes)
199 }
200}
201
202pub trait TryFromBeBytes: Sized {
203 fn try_from_be_bytes_vartime(bytes: &[u8]) -> Result<Self>;
204}
205
206pub trait UnsignedModularInt:
207 Zeroize + Clone + PartialOrd + IntegerResize<Output = Self> + TryFromBeBytes
208{
209 type Bytes: NumBytes + AsMut<[u8]>;
210 fn leading_zeros(&self) -> u32;
211 fn to_be_bytes(&self) -> Self::Bytes;
212 fn as_nz_ref(&self) -> NonZero<Self>;
213 fn bits(&self) -> u32;
214 fn bits_precision(&self) -> u32;
215 #[cfg(feature = "alloc")]
216 fn to_be_bytes_trimmed_vartime(&self) -> Box<[u8]>;
217}
218
219impl<T> NonZero<T>
220where
221 T: UnsignedModularInt,
222{
223 pub fn new(value: T) -> Option<Self> {
224 if value.bits() == 0 {
225 None
226 } else {
227 Some(Self(value))
228 }
229 }
230
231 pub fn get(self) -> T {
232 self.0
233 }
234
235 #[allow(clippy::should_implement_trait)]
236 pub fn as_ref(&self) -> &T {
237 &self.0
238 }
239
240 pub fn bits(&self) -> u32 {
241 self.0.bits()
242 }
243
244 pub fn bits_precision(&self) -> u32 {
245 self.0.bits_precision()
246 }
247
248 pub fn to_be_bytes(&self) -> T::Bytes {
249 self.0.to_be_bytes()
250 }
251
252 #[cfg(feature = "alloc")]
253 pub fn to_be_bytes_trimmed_vartime(&self) -> Box<[u8]> {
254 self.0.to_be_bytes_trimmed_vartime()
255 }
256}
257
258impl<T> Odd<T>
259where
260 T: UnsignedModularInt,
261{
262 pub fn new(value: T) -> Option<Self> {
263 let non_zero = NonZero::new(value)?;
264 let bytes = non_zero.as_ref().to_be_bytes();
265 let bytes = bytes.as_ref();
266 let is_odd = bytes.last().map(|byte| byte & 1 == 1).unwrap_or(false);
267 if is_odd {
268 Some(Self(non_zero.get()))
269 } else {
270 None
271 }
272 }
273
274 pub fn get(self) -> T {
275 self.0
276 }
277
278 #[allow(clippy::should_implement_trait)]
279 pub fn as_ref(&self) -> &T {
280 &self.0
281 }
282
283 pub fn as_nz_ref(&self) -> NonZero<T> {
284 NonZero::new(self.0.clone()).expect("odd values are non-zero")
285 }
286
287 pub fn bits_precision(&self) -> u32 {
288 self.0.bits_precision()
289 }
290}
291
292pub trait IntoMontyForm<P: ModulusParams>: Sized {
309 fn from_reduced(integer: P::Modulus, params: &P) -> Self;
311
312 fn from_value(integer: P::Modulus, params: &P) -> Self;
315}
316
317#[cfg(feature = "alloc")]
318impl IntoMontyForm<BoxedMontyParams> for BoxedMontyForm {
319 fn from_reduced(integer: BoxedUint, params: &BoxedMontyParams) -> Self {
320 BoxedMontyForm::new(integer, params)
321 }
322
323 fn from_value(integer: BoxedUint, params: &BoxedMontyParams) -> Self {
324 let modulus =
325 CryptoNonZero::new(params.modulus().as_ref().clone()).expect("modulus is non-zero");
326 let reduced = integer.rem_vartime(&modulus);
327 Self::from_reduced(reduced, params)
328 }
329}
330
331pub trait PowBoundedExp<M: ModulusParams>: Sized {
332 fn pow_bounded_exp(&self, exp: &M::Modulus, exp_bits: u32) -> Self;
333 fn retrieve(&self) -> M::Modulus;
334}
335
336#[cfg(feature = "alloc")]
337impl PowBoundedExp<BoxedMontyParams> for BoxedMontyForm {
338 fn pow_bounded_exp(&self, exp: &BoxedUint, exp_bits: u32) -> Self {
339 self.clone().pow_bounded_exp(exp, exp_bits)
340 }
341
342 fn retrieve(&self) -> BoxedUint {
343 self.clone().retrieve()
344 }
345}
346
347pub trait Pow<M: ModulusParams>: Sized {
348 fn pow(&self, exp: &M::Modulus) -> Self;
349}
350
351#[cfg(feature = "alloc")]
352impl Pow<BoxedMontyParams> for BoxedMontyForm {
353 fn pow(&self, exp: &BoxedUint) -> Self {
354 self.clone().pow(exp)
355 }
356}
357
358pub trait InvertCt<M: ModulusParams>: Sized {
371 fn invert_ct(&self) -> Option<Self>;
372}
373
374#[cfg(feature = "alloc")]
375impl InvertCt<BoxedMontyParams> for BoxedMontyForm {
376 fn invert_ct(&self) -> Option<Self> {
377 self.invert().into_option()
378 }
379}
380
381pub trait MulCt<M: ModulusParams>: Sized {
391 fn mul_ct(&self, rhs: &Self) -> Self;
392}
393
394#[cfg(feature = "alloc")]
395impl MulCt<BoxedMontyParams> for BoxedMontyForm {
396 fn mul_ct(&self, rhs: &Self) -> Self {
397 self * rhs
398 }
399}
400
401pub trait TryRandomMod: Sized {
411 fn try_random_mod<R>(rng: &mut R, modulus: &Self) -> Result<Self>
412 where
413 R: rand_core::TryCryptoRng + ?Sized;
414}
415
416#[cfg(feature = "alloc")]
417impl TryRandomMod for BoxedUint {
418 fn try_random_mod<R>(rng: &mut R, modulus: &Self) -> Result<Self>
419 where
420 R: rand_core::TryCryptoRng + ?Sized,
421 {
422 let nz = CryptoNonZero::new(modulus.clone())
423 .into_option()
424 .ok_or(Error::InvalidModulus)?;
425 <Self as crypto_bigint::RandomMod>::try_random_mod_vartime(rng, &nz).map_err(|_| Error::Rng)
426 }
427}
428
429pub trait ModulusParams: Sized {
430 type Modulus: UnsignedModularInt;
431 type MontgomeryForm: IntoMontyForm<Self> + PowBoundedExp<Self>;
432 fn modulus(&self) -> &Odd<Self::Modulus>;
433 fn bits_precision(&self) -> u32;
434}
435
436pub(crate) mod sealed {
437 pub trait CtModulusParamsSealed {}
441}
442
443pub trait CtModulusParams: ModulusParams + sealed::CtModulusParamsSealed {}
475
476#[cfg(feature = "alloc")]
477impl sealed::CtModulusParamsSealed for BoxedMontyParams {}
478#[cfg(feature = "alloc")]
479impl CtModulusParams for BoxedMontyParams {}
480
481#[cfg(feature = "alloc")]
482impl ModulusParams for BoxedMontyParams {
483 type Modulus = BoxedUint;
484 type MontgomeryForm = BoxedMontyForm;
485 fn modulus(&self) -> &Odd<Self::Modulus> {
486 const _: () = assert!(
492 core::mem::size_of::<CryptoOdd<BoxedUint>>() == core::mem::size_of::<Odd<BoxedUint>>()
493 );
494 const _: () = assert!(
495 core::mem::align_of::<CryptoOdd<BoxedUint>>()
496 == core::mem::align_of::<Odd<BoxedUint>>()
497 );
498 unsafe {
499 &*(self.modulus() as *const CryptoOdd<Self::Modulus> as *const Odd<Self::Modulus>)
500 }
501 }
502 fn bits_precision(&self) -> u32 {
503 self.bits_precision()
504 }
505}
506
507#[cfg(feature = "alloc")]
508impl IntegerResize for BoxedUint {
509 type Output = Self;
510
511 fn resize_unchecked(self, at_least_bits_precision: u32) -> Self::Output {
512 CryptoResize::resize_unchecked(self, at_least_bits_precision)
513 }
514
515 fn try_resize(self, at_least_bits_precision: u32) -> Option<Self::Output> {
516 CryptoResize::try_resize(self, at_least_bits_precision)
517 }
518}
519
520#[cfg(feature = "alloc")]
521impl UnsignedModularInt for BoxedUint {
522 type Bytes = alloc::boxed::Box<[u8]>;
523
524 fn leading_zeros(&self) -> u32 {
525 self.leading_zeros()
526 }
527
528 fn to_be_bytes(&self) -> Self::Bytes {
529 self.to_be_bytes()
530 }
531 #[cfg(feature = "alloc")]
532 fn to_be_bytes_trimmed_vartime(&self) -> Box<[u8]> {
533 self.to_be_bytes_trimmed_vartime()
534 }
535 fn as_nz_ref(&self) -> NonZero<Self> {
536 NonZero::new(self.clone()).expect("Value is non-zero")
537 }
538 fn bits(&self) -> u32 {
539 self.bits()
540 }
541 fn bits_precision(&self) -> u32 {
542 self.bits_precision()
543 }
544}
545
546#[cfg(feature = "alloc")]
547impl TryFromBeBytes for BoxedUint {
548 fn try_from_be_bytes_vartime(bytes: &[u8]) -> Result<Self> {
549 Ok(BoxedUint::from_be_slice_vartime(bytes))
550 }
551}
552
553#[cfg(all(test, feature = "modmath"))]
565mod ct_type_guarantees {
566 use super::{CtModulusParams, InvertCt, MulCt};
567 use crate::modmath_support::{ModMathForm, ModMathParams};
568 use const_num_traits::{Ct, Nct};
569 use fixed_bigint::FixedUInt;
570 use static_assertions::{assert_impl_all, assert_not_impl_any};
571 use zeroize::ZeroizeOnDrop;
572
573 assert_impl_all!(ModMathParams<FixedUInt<u8, 64, Ct>, Ct>: CtModulusParams);
582 assert_not_impl_any!(ModMathParams<FixedUInt<u8, 64, Nct>, Nct>: CtModulusParams);
583
584 assert_impl_all!(
588 ModMathForm<FixedUInt<u8, 64, Ct>, Ct>:
589 InvertCt<ModMathParams<FixedUInt<u8, 64, Ct>, Ct>>,
590 MulCt<ModMathParams<FixedUInt<u8, 64, Ct>, Ct>>
591 );
592 assert_not_impl_any!(
593 ModMathForm<FixedUInt<u8, 64, Nct>, Nct>:
594 InvertCt<ModMathParams<FixedUInt<u8, 64, Nct>, Nct>>,
595 MulCt<ModMathParams<FixedUInt<u8, 64, Nct>, Nct>>
596 );
597
598 assert_impl_all!(ModMathForm<FixedUInt<u8, 64, Ct>, Ct>: ZeroizeOnDrop);
603 assert_impl_all!(ModMathForm<FixedUInt<u8, 64, Nct>, Nct>: ZeroizeOnDrop);
604}