Skip to content

Commit

Permalink
Add Uint::gcd using Bernstein-Yang
Browse files Browse the repository at this point in the history
Adds support for computing the greatest common divisor of two numbers
using the Bernstein-Yang algorithm, which is already impl'd for
inversions.
  • Loading branch information
tarcieri committed Dec 19, 2023
1 parent b57ea5e commit bb4e34d
Show file tree
Hide file tree
Showing 5 changed files with 88 additions and 4 deletions.
34 changes: 32 additions & 2 deletions src/modular/bernstein_yang.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,11 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
/// Returns either the adjusted modular multiplicative inverse for the argument or `None`
/// depending on invertibility of the argument, i.e. its coprimality with the modulus
pub const fn inv(&self, value: &Uint<SAT_LIMBS>) -> ConstCtOption<Uint<SAT_LIMBS>> {
let (mut d, mut e) = (Int62L::ZERO, self.adjuster);
let mut d = Int62L::ZERO;
let mut e = self.adjuster;
let mut f = self.modulus;
let mut g = Int62L::from_uint(value);
let (mut delta, mut f) = (1, self.modulus);
let mut delta = 1;
let mut matrix;

while !g.eq(&Int62L::ZERO) {
Expand All @@ -93,6 +95,34 @@ impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize>
ConstCtOption::new(ret.to_uint(), is_some)
}

/// Returns the greatest common divisor (GCD) of the two given numbers.
///
/// This is defined on this type to piggyback on the definitions for `SAT_LIMBS` and `UNSAT_LIMBS` which are
/// computed when defining `PrecomputeInverter::Inverter` for various `Uint` limb sizes.
pub(crate) fn gcd(f: &Uint<SAT_LIMBS>, g: &Uint<SAT_LIMBS>) -> Uint<SAT_LIMBS> {
let f_0 = Int62L::from_uint(f);
let inverse = inv_mod2_62(f.as_words());

let mut d = Int62L::ZERO;
let mut e = Int62L::ONE;
let mut f = f_0;
let mut g = Int62L::from_uint(g);
let mut delta = 1;
let mut matrix;

while !g.eq(&Int62L::ZERO) {
(delta, matrix) = Self::jump(&f, &g, delta);
(f, g) = fg(f, g, matrix);
(d, e) = de(&f_0, inverse, d, e, matrix);
}

if f.is_negative() {
f = f.neg();
}

f.to_uint()
}

/// Returns the Bernstein-Yang transition matrix multiplied by 2^62 and the new value
/// of the delta variable for the 62 basic steps of the Bernstein-Yang method, which
/// are to be performed sequentially for specified initial values of f, g and delta
Expand Down
2 changes: 2 additions & 0 deletions src/uint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ mod div;
pub(crate) mod div_limb;
mod encoding;
mod from;
mod gcd;
mod inv_mod;
pub(crate) mod mul;
mod mul_mod;
Expand Down Expand Up @@ -431,6 +432,7 @@ impl_uint_concat_split_mixed! {

#[cfg(feature = "extra-sizes")]
mod extra_sizes;

#[cfg(feature = "extra-sizes")]
pub use extra_sizes::*;

Expand Down
30 changes: 30 additions & 0 deletions src/uint/gcd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//! Support for computing greatest common divisor of two `Uint`s.
use super::Uint;
use crate::{modular::BernsteinYangInverter, PrecomputeInverter};

impl<const SAT_LIMBS: usize, const UNSAT_LIMBS: usize> Uint<SAT_LIMBS>
where
Self: PrecomputeInverter<Inverter = BernsteinYangInverter<SAT_LIMBS, UNSAT_LIMBS>>,
{
/// Compute the greatest common divisor (GCD) of this number and another.
///
/// Panics if `self` is odd.
pub fn gcd(&self, rhs: &Self) -> Self {
debug_assert!(bool::from(self.is_odd()));
<Self as PrecomputeInverter>::Inverter::gcd(self, rhs)
}
}

#[cfg(test)]
mod tests {
use crate::U256;

#[test]
fn gcd() {
let f = U256::from(4391633u32);
let g = U256::from(2022161u32);
let gcd = f.gcd(&g);
assert_eq!(gcd, U256::from(1763u32));
}
}
7 changes: 7 additions & 0 deletions tests/uint_proptests.proptest-regressions
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 109dd02159bd1afed3bdf3b3a82407df5df8d5949115574175225e7e72d13056 # shrinks to a = Uint(0x0000000000000000000000000000000000000000000000000000000000000006), b = Uint(0x0000000000000000000000000000000000000000000000000000000000000068)
19 changes: 17 additions & 2 deletions tests/uint_proptests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
use crypto_bigint::{
modular::{DynResidue, DynResidueParams},
Encoding, Limb, NonZero, Word, U256,
Encoding, Integer, Limb, NonZero, Word, U256,
};
use num_bigint::BigUint;
use num_integer::Integer;
use num_integer::Integer as _;
use num_traits::identities::{One, Zero};
use proptest::prelude::*;
use std::mem;
Expand Down Expand Up @@ -275,6 +275,21 @@ proptest! {
}
}

#[test]
fn gcd(mut f in uint(), g in uint()) {
if f.is_even().into() {
f = f.wrapping_add(&U256::ONE);
}

let f_bi = to_biguint(&f);
let g_bi = to_biguint(&g);

let expected = to_uint(f_bi.gcd(&g_bi));
let actual = f.gcd(&g);

assert_eq!(expected, actual);
}

#[test]
fn inv_mod2k(a in uint(), k in any::<u32>()) {
let a = a | U256::ONE; // make odd
Expand Down

0 comments on commit bb4e34d

Please sign in to comment.