Skip to content

Commit a5663d7

Browse files
authored
Add BoxedBernsteinYangInverter (#493)
Adds a boxed quivalent of `BernsteinYangInverter` which can operate on arbitrarily-sized integers. Note: initially added #460 but it was deemed buggy and removed. This new implementation has extensive tests which hopefully ensure correctness.
1 parent 19840ae commit a5663d7

File tree

12 files changed

+816
-65
lines changed

12 files changed

+816
-65
lines changed

src/modular.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,10 @@ pub use self::{
3838
};
3939

4040
#[cfg(feature = "alloc")]
41-
pub use self::boxed_monty_form::{BoxedMontyForm, BoxedMontyParams};
41+
pub use self::{
42+
bernstein_yang::boxed::BoxedBernsteinYangInverter,
43+
boxed_monty_form::{BoxedMontyForm, BoxedMontyParams},
44+
};
4245

4346
/// A generalization for numbers kept in optimized representations (e.g. Montgomery)
4447
/// that can be converted back to the original form.

src/modular/bernstein_yang.rs

Lines changed: 102 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@
1313
#[macro_use]
1414
mod macros;
1515

16+
#[cfg(feature = "alloc")]
17+
pub(crate) mod boxed;
18+
1619
use crate::{ConstChoice, ConstCtOption, Inverter, Limb, Odd, Uint, Word};
1720
use subtle::CtOption;
1821

@@ -80,7 +83,7 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
8083
let mut matrix;
8184

8285
while !g.eq(&Int62L::ZERO) {
83-
(delta, matrix) = Self::jump(&f, &g, delta);
86+
(delta, matrix) = jump(&f.0, &g.0, delta);
8487
(f, g) = fg(f, g, matrix);
8588
(d, e) = de(&self.modulus, self.inverse, d, e, matrix);
8689
}
@@ -99,7 +102,7 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
99102
/// `UNSAT_LIMBS` which are computed when defining `PrecomputeInverter::Inverter` for various
100103
/// `Uint` limb sizes.
101104
pub(crate) const fn gcd(f: &Uint<SAT_LIMBS>, g: &Uint<SAT_LIMBS>) -> Uint<SAT_LIMBS> {
102-
let f_0 = Int62L::from_uint(f);
105+
let f_0 = Int62L::<UNSAT_LIMBS>::from_uint(f);
103106
let inverse = inv_mod2_62(f.as_words());
104107

105108
let mut d = Int62L::ZERO;
@@ -110,7 +113,7 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
110113
let mut matrix;
111114

112115
while !g.eq(&Int62L::ZERO) {
113-
(delta, matrix) = Self::jump(&f, &g, delta);
116+
(delta, matrix) = jump(&f.0, &g.0, delta);
114117
(f, g) = fg(f, g, matrix);
115118
(d, e) = de(&f_0, inverse, d, e, matrix);
116119
}
@@ -122,52 +125,6 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
122125
f.to_uint()
123126
}
124127

125-
/// Returns the Bernstein-Yang transition matrix multiplied by 2^62 and the new value of the
126-
/// delta variable for the 62 basic steps of the Bernstein-Yang method, which are to be
127-
/// performed sequentially for specified initial values of f, g and delta
128-
const fn jump(
129-
f: &Int62L<UNSAT_LIMBS>,
130-
g: &Int62L<UNSAT_LIMBS>,
131-
mut delta: i64,
132-
) -> (i64, Matrix) {
133-
// This function is defined because the method "min" of the i64 type is not constant
134-
const fn min(a: i64, b: i64) -> i64 {
135-
if a > b {
136-
b
137-
} else {
138-
a
139-
}
140-
}
141-
142-
let (mut steps, mut f, mut g) = (62, f.lowest() as i64, g.lowest() as i128);
143-
let mut t: Matrix = [[1, 0], [0, 1]];
144-
145-
loop {
146-
let zeros = min(steps, g.trailing_zeros() as i64);
147-
(steps, delta, g) = (steps - zeros, delta + zeros, g >> zeros);
148-
t[0] = [t[0][0] << zeros, t[0][1] << zeros];
149-
150-
if steps == 0 {
151-
break;
152-
}
153-
if delta > 0 {
154-
(delta, f, g) = (-delta, g as i64, -f as i128);
155-
(t[0], t[1]) = (t[1], [-t[0][0], -t[0][1]]);
156-
}
157-
158-
// The formula (3 * x) xor 28 = -1 / x (mod 32) for an odd integer x in the two's
159-
// complement code has been derived from the formula (3 * x) xor 2 = 1 / x (mod 32)
160-
// attributed to Peter Montgomery.
161-
let mask = (1 << min(min(steps, 1 - delta), 5)) - 1;
162-
let w = (g as i64).wrapping_mul(f.wrapping_mul(3) ^ 28) & mask;
163-
164-
t[1] = [t[0][0] * w + t[1][0], t[0][1] * w + t[1][1]];
165-
g += w as i128 * f as i128;
166-
}
167-
168-
(delta, t)
169-
}
170-
171128
/// Returns either "value (mod M)" or "-value (mod M)", where M is the modulus the inverter
172129
/// was created for, depending on "negate", which determines the presence of "-" in the used
173130
/// formula. The input integer lies in the interval (-2 * M, M).
@@ -207,8 +164,14 @@ const fn inv_mod2_62(value: &[Word]) -> i64 {
207164
let value = {
208165
#[cfg(target_pointer_width = "32")]
209166
{
210-
debug_assert!(value.len() >= 2);
211-
value[0] as u64 | (value[1] as u64) << 32
167+
debug_assert!(value.len() >= 1);
168+
let mut ret = value[0] as u64;
169+
170+
if value.len() >= 2 {
171+
ret |= (value[1] as u64) << 32;
172+
}
173+
174+
ret
212175
}
213176

214177
#[cfg(target_pointer_width = "64")]
@@ -225,6 +188,48 @@ const fn inv_mod2_62(value: &[Word]) -> i64 {
225188
(x.wrapping_mul(y.wrapping_add(1)) & (u64::MAX >> 2)) as i64
226189
}
227190

191+
/// Returns the Bernstein-Yang transition matrix multiplied by 2^62 and the new value of the
192+
/// delta variable for the 62 basic steps of the Bernstein-Yang method, which are to be
193+
/// performed sequentially for specified initial values of f, g and delta
194+
const fn jump(f: &[u64], g: &[u64], mut delta: i64) -> (i64, Matrix) {
195+
// This function is defined because the method "min" of the i64 type is not constant
196+
const fn min(a: i64, b: i64) -> i64 {
197+
if a > b {
198+
b
199+
} else {
200+
a
201+
}
202+
}
203+
204+
let (mut steps, mut f, mut g) = (62, f[0] as i64, g[0] as i128);
205+
let mut t: Matrix = [[1, 0], [0, 1]];
206+
207+
loop {
208+
let zeros = min(steps, g.trailing_zeros() as i64);
209+
(steps, delta, g) = (steps - zeros, delta + zeros, g >> zeros);
210+
t[0] = [t[0][0] << zeros, t[0][1] << zeros];
211+
212+
if steps == 0 {
213+
break;
214+
}
215+
if delta > 0 {
216+
(delta, f, g) = (-delta, g as i64, -f as i128);
217+
(t[0], t[1]) = (t[1], [-t[0][0], -t[0][1]]);
218+
}
219+
220+
// The formula (3 * x) xor 28 = -1 / x (mod 32) for an odd integer x in the two's
221+
// complement code has been derived from the formula (3 * x) xor 2 = 1 / x (mod 32)
222+
// attributed to Peter Montgomery.
223+
let mask = (1 << min(min(steps, 1 - delta), 5)) - 1;
224+
let w = (g as i64).wrapping_mul(f.wrapping_mul(3) ^ 28) & mask;
225+
226+
t[1] = [t[0][0] * w + t[1][0], t[0][1] * w + t[1][1]];
227+
g += w as i128 * f as i128;
228+
}
229+
230+
(delta, t)
231+
}
232+
228233
/// Returns the updated values of the variables f and g for specified initial ones and
229234
/// Bernstein-Yang transition matrix multiplied by 2^62. The returned vector is
230235
/// "matrix * (f, g)' / 2^62", where "'" is the transpose operator.
@@ -329,14 +334,12 @@ impl<const LIMBS: usize> Int62L<LIMBS> {
329334
/// The ordering of the chunks in these arrays is little-endian
330335
#[allow(trivial_numeric_casts, clippy::wrong_self_convention)]
331336
pub const fn to_uint<const SAT_LIMBS: usize>(&self) -> Uint<SAT_LIMBS> {
337+
debug_assert!(!self.is_negative(), "can't convert negative number to Uint");
338+
332339
if LIMBS != bernstein_yang_nlimbs!(SAT_LIMBS * Limb::BITS as usize) {
333340
panic!("incorrect number of limbs");
334341
}
335342

336-
if self.is_negative() {
337-
panic!("can't convert negative number to Uint");
338-
}
339-
340343
let mut ret = [0 as Word; SAT_LIMBS];
341344
impl_limb_convert!(u64, 62, &self.0, Word, Word::BITS as usize, ret);
342345
Uint::from_words(ret)
@@ -366,8 +369,9 @@ impl<const LIMBS: usize> Int62L<LIMBS> {
366369
//
367370
// Since for the two's complement code the additive negation is the result of adding 1 to
368371
// the bitwise inverted argument's representation, for any encoded integers x and y we have
369-
// x * y = (-x) * (-y) = (!x + 1) * (-y) = !x * (-y) + (-y), where "!" is the bitwise
372+
// x * y = (-x) * (-y) = (!x + 1) * (-y) = !x * (-y) + (-y), where "!" is the bitwise
370373
// inversion and arithmetic operations are performed according to the rules of the code.
374+
//
371375
// If the short multiplicand is negative, the algorithm below uses this formula by
372376
// substituting the short multiplicand for y and turns into the modified standard
373377
// multiplication algorithm, where the carry flag is initialized with the additively
@@ -454,8 +458,26 @@ impl<const LIMBS: usize> PartialEq for Int62L<LIMBS> {
454458

455459
#[cfg(test)]
456460
mod tests {
461+
use crate::{Inverter, PrecomputeInverter, U256};
462+
457463
type Int62L = super::Int62L<4>;
458464

465+
#[test]
466+
fn invert() {
467+
let g =
468+
U256::from_be_hex("00000000CBF9350842F498CE441FC2DC23C7BF47D3DE91C327B2157C5E4EED77");
469+
let modulus =
470+
U256::from_be_hex("FFFFFFFF00000000FFFFFFFFFFFFFFFFBCE6FAADA7179E84F3B9CAC2FC632551")
471+
.to_odd()
472+
.unwrap();
473+
let inverter = modulus.precompute_inverter();
474+
let result = inverter.invert(&g).unwrap();
475+
assert_eq!(
476+
U256::from_be_hex("FB668F8F509790BC549B077098918604283D42901C92981062EB48BC723F617B"),
477+
result
478+
);
479+
}
480+
459481
#[test]
460482
fn int62l_add() {
461483
assert_eq!(Int62L::ZERO, Int62L::ZERO.add(&Int62L::ZERO));
@@ -486,4 +508,28 @@ mod tests {
486508
assert!(!Int62L::ONE.is_negative());
487509
assert!(Int62L::MINUS_ONE.is_negative());
488510
}
511+
512+
#[test]
513+
fn int62l_shr() {
514+
let n = super::Int62L([
515+
0,
516+
1211048314408256470,
517+
1344008336933394898,
518+
3913497193346473913,
519+
2764114971089162538,
520+
4,
521+
]);
522+
523+
assert_eq!(
524+
&n.shr().0,
525+
&[
526+
1211048314408256470,
527+
1344008336933394898,
528+
3913497193346473913,
529+
2764114971089162538,
530+
4,
531+
0
532+
]
533+
);
534+
}
489535
}

0 commit comments

Comments
 (0)