Skip to content

Commit 5b2e903

Browse files
authored
Implement modular arithmetic; MSRV 1.61 (#130)
Adds the following functionality: - Shifting left and right by 1 and returning the overflow as a `Choice` - `Neg` for `Wrapping<Uint<LIMBS>>` - Modular multiplicative inverse - Computing parameters for the Montgomery form (MF) - Going to and from MF - Modular addition (in MF) - Modular multiplication (in MF) - Modular exponentiation (in MF)
1 parent 7b10380 commit 5b2e903

29 files changed

+1178
-15
lines changed

.github/workflows/crypto-bigint.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ jobs:
1919
strategy:
2020
matrix:
2121
rust:
22-
- 1.57.0 # MSRV
22+
- 1.61.0 # MSRV
2323
- stable
2424
target:
2525
- thumbv7em-none-eabi
@@ -49,15 +49,15 @@ jobs:
4949
include:
5050
# 32-bit Linux
5151
- target: i686-unknown-linux-gnu
52-
rust: 1.57.0 # MSRV
52+
rust: 1.61.0 # MSRV
5353
deps: sudo apt update && sudo apt install gcc-multilib
5454
- target: i686-unknown-linux-gnu
5555
rust: stable
5656
deps: sudo apt update && sudo apt install gcc-multilib
5757

5858
# 64-bit Linux
5959
- target: x86_64-unknown-linux-gnu
60-
rust: 1.57.0 # MSRV
60+
rust: 1.61.0 # MSRV
6161
- target: x86_64-unknown-linux-gnu
6262
rust: stable
6363
steps:
@@ -122,7 +122,7 @@ jobs:
122122
- uses: actions/checkout@v3
123123
- uses: actions-rs/toolchain@v1
124124
with:
125-
toolchain: 1.57.0
125+
toolchain: 1.61.0
126126
components: clippy
127127
override: true
128128
profile: minimal

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ keywords = ["arbitrary", "crypto", "bignum", "integer", "precision"]
1414
readme = "README.md"
1515
resolver = "2"
1616
edition = "2021"
17-
rust-version = "1.57"
17+
rust-version = "1.61"
1818

1919
[dependencies]
2020
subtle = { version = "2.4", default-features = false }

src/lib.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@
4747
//! pub const MODULUS_SHR1: U256 = MODULUS.shr_vartime(1);
4848
//! ```
4949
//!
50+
//! Note that large constant computations may accidentally trigger a the `const_eval_limit` of the compiler.
51+
//! The current way to deal with this problem is to either simplify this computation,
52+
//! or increase the compiler's limit (currently a nightly feature).
53+
//! One can completely remove the compiler's limit using:
54+
//! ```ignore
55+
//! #![feature(const_eval_limit)]
56+
//! #![const_eval_limit = "0"]
57+
//! ```
58+
//!
5059
//! ### Trait-based usage
5160
//!
5261
//! The [`UInt`] type itself does not implement the standard arithmetic traits
@@ -100,6 +109,10 @@
100109
//! assert_eq!(b, U256::ZERO);
101110
//! ```
102111
//!
112+
//! It also supports modular arithmetic over constant moduli using `Residue`.
113+
//! That includes modular exponentiation and multiplicative inverses.
114+
//! These features are described in the [`modular`] module.
115+
//!
103116
//! ### Random number generation
104117
//!
105118
//! When the `rand_core` or `rand` features of this crate are enabled, it's

src/uint.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ mod from;
2525
mod inv_mod;
2626
mod mul;
2727
mod mul_mod;
28+
mod neg;
2829
mod neg_mod;
2930
mod resize;
3031
mod shl;
@@ -33,6 +34,9 @@ mod sqrt;
3334
mod sub;
3435
mod sub_mod;
3536

37+
/// Implements modular arithmetic for constant moduli.
38+
pub mod modular;
39+
3640
#[cfg(feature = "generic-array")]
3741
mod array;
3842

src/uint/add.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! [`UInt`] addition operations.
22
3-
use crate::{Checked, CheckedAdd, Limb, UInt, Wrapping, Zero};
3+
use crate::{Checked, CheckedAdd, Limb, UInt, Word, Wrapping, Zero};
44
use core::ops::{Add, AddAssign};
55
use subtle::CtOption;
66

@@ -36,6 +36,14 @@ impl<const LIMBS: usize> UInt<LIMBS> {
3636
pub const fn wrapping_add(&self, rhs: &Self) -> Self {
3737
self.adc(rhs, Limb::ZERO).0
3838
}
39+
40+
/// Perform wrapping addition, returning the overflow bit as a `Word` that is either 0...0 or 1...1.
41+
pub(crate) const fn conditional_wrapping_add(&self, rhs: &Self, choice: Word) -> (Self, Word) {
42+
let actual_rhs = UInt::ct_select(UInt::ZERO, *rhs, choice);
43+
let (sum, carry) = self.adc(&actual_rhs, Limb::ZERO);
44+
45+
(sum, carry.0.wrapping_mul(Word::MAX))
46+
}
3947
}
4048

4149
impl<const LIMBS: usize> CheckedAdd<&UInt<LIMBS>> for UInt<LIMBS> {

src/uint/bit_and.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ impl<const LIMBS: usize> BitAnd for UInt<LIMBS> {
4646
impl<const LIMBS: usize> BitAnd<&UInt<LIMBS>> for UInt<LIMBS> {
4747
type Output = UInt<LIMBS>;
4848

49+
#[allow(clippy::needless_borrow)]
4950
fn bitand(self, rhs: &UInt<LIMBS>) -> UInt<LIMBS> {
5051
(&self).bitand(rhs)
5152
}

src/uint/bit_not.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ impl<const LIMBS: usize> UInt<LIMBS> {
2323
impl<const LIMBS: usize> Not for UInt<LIMBS> {
2424
type Output = Self;
2525

26+
#[allow(clippy::needless_borrow)]
2627
fn not(self) -> <Self as Not>::Output {
2728
(&self).not()
2829
}

src/uint/bit_or.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ impl<const LIMBS: usize> BitOr for UInt<LIMBS> {
4646
impl<const LIMBS: usize> BitOr<&UInt<LIMBS>> for UInt<LIMBS> {
4747
type Output = UInt<LIMBS>;
4848

49+
#[allow(clippy::needless_borrow)]
4950
fn bitor(self, rhs: &UInt<LIMBS>) -> UInt<LIMBS> {
5051
(&self).bitor(rhs)
5152
}

src/uint/bit_xor.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ impl<const LIMBS: usize> BitXor for UInt<LIMBS> {
4646
impl<const LIMBS: usize> BitXor<&UInt<LIMBS>> for UInt<LIMBS> {
4747
type Output = UInt<LIMBS>;
4848

49+
#[allow(clippy::needless_borrow)]
4950
fn bitxor(self, rhs: &UInt<LIMBS>) -> UInt<LIMBS> {
5051
(&self).bitxor(rhs)
5152
}

src/uint/cmp.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,14 @@ impl<const LIMBS: usize> UInt<LIMBS> {
2424
UInt { limbs }
2525
}
2626

27+
#[inline]
28+
pub(crate) const fn ct_swap(a: UInt<LIMBS>, b: UInt<LIMBS>, c: Word) -> (Self, Self) {
29+
let new_a = Self::ct_select(a, b, c);
30+
let new_b = Self::ct_select(b, a, c);
31+
32+
(new_a, new_b)
33+
}
34+
2735
/// Returns all 1's if `self`!=0 or 0 if `self`==0.
2836
///
2937
/// Const-friendly: we can't yet use `subtle` in `const fn` contexts.
@@ -38,6 +46,10 @@ impl<const LIMBS: usize> UInt<LIMBS> {
3846
Limb::is_nonzero(Limb(b))
3947
}
4048

49+
pub(crate) const fn ct_is_odd(&self) -> Word {
50+
(self.limbs[0].0 & 1).wrapping_mul(Word::MAX)
51+
}
52+
4153
/// Returns -1 if self < rhs
4254
/// 0 if self == rhs
4355
/// 1 if self > rhs

src/uint/div.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,43 @@ impl<const LIMBS: usize> UInt<LIMBS> {
6868
(rem, (is_some & 1) as u8)
6969
}
7070

71+
/// Computes `self` % `rhs`, returns the remainder and
72+
/// and 1 for is_some or 0 for is_none. The results can be wrapped in [`CtOption`].
73+
/// NOTE: Use only if you need to access const fn. Otherwise use `reduce`
74+
/// This is variable only with respect to `rhs`.
75+
///
76+
/// When used with a fixed `rhs`, this function is constant-time with respect
77+
/// to `self`.
78+
#[allow(dead_code)]
79+
pub(crate) const fn ct_reduce_wide(lower_upper: (Self, Self), rhs: &Self) -> (Self, u8) {
80+
let mb = rhs.bits_vartime();
81+
82+
// The number of bits to consider is two sets of limbs * BIT_SIZE - mb (modulus bitcount)
83+
let mut bd = (2 * LIMBS * Limb::BIT_SIZE) - mb;
84+
85+
// The wide integer to reduce, split into two halves
86+
let (mut lower, mut upper) = lower_upper;
87+
88+
// Factor of the modulus, split into two halves
89+
let mut c = Self::shl_vartime_wide((*rhs, UInt::ZERO), bd);
90+
91+
loop {
92+
let (lower_sub, borrow) = lower.sbb(&c.0, Limb::ZERO);
93+
let (upper_sub, borrow) = upper.sbb(&c.1, borrow);
94+
95+
lower = Self::ct_select(lower_sub, lower, borrow.0);
96+
upper = Self::ct_select(upper_sub, upper, borrow.0);
97+
if bd == 0 {
98+
break;
99+
}
100+
bd -= 1;
101+
c = Self::shr_vartime_wide(c, 1);
102+
}
103+
104+
let is_some = Limb(mb as Word).is_nonzero();
105+
(lower, (is_some & 1) as u8)
106+
}
107+
71108
/// Computes `self` % 2^k. Faster than reduce since its a power of 2.
72109
/// Limited to 2^16-1 since UInt doesn't support higher.
73110
pub const fn reduce2k(&self, k: usize) -> Self {
@@ -466,6 +503,19 @@ mod tests {
466503
assert_eq!(r, U256::from(3u8));
467504
}
468505

506+
#[test]
507+
fn reduce_tests_wide_zero_padded() {
508+
let (r, is_some) = U256::ct_reduce_wide((U256::from(10u8), U256::ZERO), &U256::from(2u8));
509+
assert_eq!(is_some, 1);
510+
assert_eq!(r, U256::ZERO);
511+
let (r, is_some) = U256::ct_reduce_wide((U256::from(10u8), U256::ZERO), &U256::from(3u8));
512+
assert_eq!(is_some, 1);
513+
assert_eq!(r, U256::ONE);
514+
let (r, is_some) = U256::ct_reduce_wide((U256::from(10u8), U256::ZERO), &U256::from(7u8));
515+
assert_eq!(is_some, 1);
516+
assert_eq!(r, U256::from(3u8));
517+
}
518+
469519
#[test]
470520
fn reduce_max() {
471521
let mut a = U256::ZERO;

src/uint/inv_mod.rs

Lines changed: 96 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
use subtle::{Choice, CtOption};
2+
13
use super::UInt;
2-
use crate::Limb;
4+
use crate::{Limb, Word};
35

46
impl<const LIMBS: usize> UInt<LIMBS> {
57
/// Computes 1/`self` mod 2^k as specified in Algorithm 4 from
@@ -25,11 +27,72 @@ impl<const LIMBS: usize> UInt<LIMBS> {
2527
}
2628
x
2729
}
30+
31+
/// Computes the multiplicative inverse of `self` mod `modulus`. In other words `self^-1 mod modulus`. Returns `(inverse, 1...1)` if an inverse exists, otherwise `(undefined, 0...0)`. The algorithm is the same as in GMP 6.2.1's `mpn_sec_invert`.
32+
pub const fn inv_odd_mod(self, modulus: UInt<LIMBS>) -> (Self, Word) {
33+
debug_assert!(modulus.ct_is_odd() == Word::MAX);
34+
35+
let mut a = self;
36+
37+
let mut u = UInt::ONE;
38+
let mut v = UInt::ZERO;
39+
40+
let mut b = modulus;
41+
42+
// TODO: This can be lower if `self` is known to be small.
43+
let bit_size = 2 * LIMBS * 64;
44+
45+
let mut m1hp = modulus;
46+
let (m1hp_new, carry) = m1hp.shr_1();
47+
debug_assert!(carry == Word::MAX);
48+
m1hp = m1hp_new.wrapping_add(&UInt::ONE);
49+
50+
let mut i = 0;
51+
while i < bit_size {
52+
debug_assert!(b.ct_is_odd() == Word::MAX);
53+
54+
let self_odd = a.ct_is_odd();
55+
56+
// Set `self -= b` if `self` is odd.
57+
let (new_a, swap) = a.conditional_wrapping_sub(&b, self_odd);
58+
// Set `b += self` if `swap` is true.
59+
b = UInt::ct_select(b, b.wrapping_add(&new_a), swap);
60+
// Negate `self` if `swap` is true.
61+
a = new_a.conditional_wrapping_neg(swap);
62+
63+
let (new_u, new_v) = UInt::ct_swap(u, v, swap);
64+
let (new_u, cy) = new_u.conditional_wrapping_sub(&new_v, self_odd);
65+
let (new_u, cyy) = new_u.conditional_wrapping_add(&modulus, cy);
66+
debug_assert!(cy == cyy);
67+
68+
let (new_a, overflow) = a.shr_1();
69+
debug_assert!(overflow == 0);
70+
let (new_u, cy) = new_u.shr_1();
71+
let (new_u, cy) = new_u.conditional_wrapping_add(&m1hp, cy);
72+
debug_assert!(cy == 0);
73+
74+
a = new_a;
75+
u = new_u;
76+
v = new_v;
77+
78+
i += 1;
79+
}
80+
81+
debug_assert!(a.ct_cmp(&UInt::ZERO) == 0);
82+
83+
(v, b.ct_not_eq(&UInt::ONE) ^ Word::MAX)
84+
}
85+
86+
/// Computes the multiplicative inverse of `self` mod `modulus`. In other words `self^-1 mod modulus`. Returns `None` if the inverse does not exist. The algorithm is the same as in GMP 6.2.1's `mpn_sec_invert`.
87+
pub fn inv_odd_mod_option(self, modulus: UInt<LIMBS>) -> CtOption<Self> {
88+
let (inverse, exists) = self.inv_odd_mod(modulus);
89+
CtOption::new(inverse, Choice::from((exists == Word::MAX) as u8))
90+
}
2891
}
2992

3093
#[cfg(test)]
3194
mod tests {
32-
use crate::U256;
95+
use crate::{U1024, U256, U64};
3396

3497
#[test]
3598
fn inv_mod2k() {
@@ -59,4 +122,35 @@ mod tests {
59122
let a = v.inv_mod2k(256);
60123
assert_eq!(e, a);
61124
}
125+
126+
#[test]
127+
fn test_invert() {
128+
let a = U1024::from_be_hex("000225E99153B467A5B451979A3F451DAEF3BF8D6C6521D2FA24BBB17F29544E347A412B065B75A351EA9719E2430D2477B11CC9CF9C1AD6EDEE26CB15F463F8BCC72EF87EA30288E95A48AA792226CEC959DCB0672D8F9D80A54CBBEA85CAD8382EC224DEB2F5784E62D0CC2F81C2E6AD14EBABE646D6764B30C32B87688985");
129+
let m = U1024::from_be_hex("D509E7854ABDC81921F669F1DC6F61359523F3949803E58ED4EA8BC16483DC6F37BFE27A9AC9EEA2969B357ABC5C0EE214BE16A7D4C58FC620D5B5A20AFF001AD198D3155E5799DC4EA76652D64983A7E130B5EACEBAC768D28D589C36EC749C558D0B64E37CD0775C0D0104AE7D98BA23C815185DD43CD8B16292FD94156767");
130+
131+
let res = a.inv_odd_mod_option(m);
132+
133+
let expected = U1024::from_be_hex("B03623284B0EBABCABD5C5881893320281460C0A8E7BF4BFDCFFCBCCBF436A55D364235C8171E46C7D21AAD0680676E57274A8FDA6D12768EF961CACDD2DAE5788D93DA5EB8EDC391EE3726CDCF4613C539F7D23E8702200CB31B5ED5B06E5CA3E520968399B4017BF98A864FABA2B647EFC4998B56774D4F2CB026BC024A336");
134+
assert_eq!(res.unwrap(), expected);
135+
}
136+
137+
#[test]
138+
fn test_invert_small() {
139+
let a = U64::from(3u64);
140+
let m = U64::from(13u64);
141+
142+
let res = a.inv_odd_mod_option(m);
143+
144+
assert_eq!(U64::from(9u64), res.unwrap());
145+
}
146+
147+
#[test]
148+
fn test_no_inverse_small() {
149+
let a = U64::from(14u64);
150+
let m = U64::from(49u64);
151+
152+
let res = a.inv_odd_mod_option(m);
153+
154+
assert!(res.is_none().unwrap_u8() == 1);
155+
}
62156
}

src/uint/modular/add.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
use crate::UInt;
2+
3+
pub trait AddResidue {
4+
/// Computes the (reduced) sum of two residues.
5+
fn add(&self, rhs: &Self) -> Self;
6+
}
7+
8+
pub(crate) const fn add_montgomery_form<const LIMBS: usize>(
9+
a: &UInt<LIMBS>,
10+
b: &UInt<LIMBS>,
11+
modulus: &UInt<LIMBS>,
12+
) -> UInt<LIMBS> {
13+
a.add_mod(b, modulus)
14+
}

0 commit comments

Comments
 (0)