diff options
| author | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2021-08-21 17:39:18 +0200 |
|---|---|---|
| committer | Jeroen van Rijn <Kelimion@users.noreply.github.com> | 2021-08-27 16:41:16 +0200 |
| commit | 4153898c557155be07565c35be4bf9f95fd27503 (patch) | |
| tree | 37647fbccf9ce76da6c1015b797b21786801b362 | |
| parent | 33df335ec94e403191e05c711a0e21a79be28f5b (diff) | |
big: Add Montgomery Reduction.
| -rw-r--r-- | core/math/big/prime.odin | 110 |
1 files changed, 110 insertions, 0 deletions
diff --git a/core/math/big/prime.odin b/core/math/big/prime.odin index 9450060b7..f35e02807 100644 --- a/core/math/big/prime.odin +++ b/core/math/big/prime.odin @@ -33,6 +33,100 @@ int_prime_is_divisible :: proc(a: ^Int, allocator := context.allocator) -> (res: return false, nil; } +/* + Computes xR**-1 == x (mod N) via Montgomery Reduction. +*/ +internal_int_montgomery_reduce :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) { + context.allocator = allocator; + /* + Can the fast reduction [comba] method be used? + Note that unlike in mul, you're safely allowed *less* than the available columns [255 per default], + since carries are fixed up in the inner loop. + */ + digs := (n.used * 2) + 1; + if digs < _WARRAY && x.used <= _WARRAY && n.used < _MAX_COMBA { + return _private_montgomery_reduce_comba(x, n, rho); + } + + /* + Grow the input as required + */ + internal_grow(x, digs) or_return; + x.used = digs; + + for ix := 0; ix < n.used; ix += 1 { + /* + `mu = ai * rho mod b` + The value of rho must be precalculated via `int_montgomery_setup()`, + such that it equals -1/n0 mod b this allows the following inner loop + to reduce the input one digit at a time. + */ + + mu := DIGIT((_WORD(x.digit[ix]) * _WORD(rho)) & _WORD(_MASK)); + + /* + a = a + mu * m * b**i + Multiply and add in place. + */ + u := DIGIT(0); + iy := int(0); + for ; iy < n.used; iy += 1 { + /* + Compute product and sum. + */ + r := (_WORD(mu) * _WORD(n.digit[iy]) + _WORD(u) + _WORD(x.digit[ix + iy])); + + /* + Get carry. + */ + u = DIGIT(r >> _DIGIT_BITS); + + /* + Fix digit. + */ + x.digit[ix + iy] = DIGIT(r & _WORD(_MASK)); + } + + /* + At this point the ix'th digit of x should be zero. + Propagate carries upwards as required. + */ + for u != 0 { + x.digit[ix + iy] += u; + u = x.digit[ix + iy] >> _DIGIT_BITS; + x.digit[ix + iy] &= _MASK; + iy += 1; + } + } + + /* + At this point the n.used'th least significant digits of x are all zero, + which means we can shift x to the right by n.used digits and the + residue is unchanged. + + x = x/b**n.used. + */ + internal_clamp(x); + internal_shr_digit(x, n.used); + + /* + if x >= n then x = x - n + */ + if internal_cmp_mag(x, n) != -1 { + return internal_sub(x, x, n); + } + + return nil; +} + +int_montgomery_reduce :: proc(x, n: ^Int, rho: DIGIT, allocator := context.allocator) -> (err: Error) { + assert_if_nil(x, n); + context.allocator = allocator; + + internal_clear_if_uninitialized(x, n) or_return; + + return #force_inline internal_int_montgomery_reduce(x, n, rho); +} /* Shifts with subtractions when the result is greater than b. @@ -67,6 +161,15 @@ internal_int_montgomery_calc_normalization :: proc(a, b: ^Int, allocator := cont return nil; } +int_montgomery_calc_normalization :: proc(a, b: ^Int, allocator := context.allocator) -> (err: Error) { + assert_if_nil(a, b); + context.allocator = allocator; + + internal_clear_if_uninitialized(a, b) or_return; + + return #force_inline internal_int_montgomery_calc_normalization(a, b); +} + /* Sets up the Montgomery reduction stuff. */ @@ -97,6 +200,13 @@ internal_int_montgomery_setup :: proc(n: ^Int) -> (rho: DIGIT, err: Error) { return rho, nil; } +int_montgomery_setup :: proc(n: ^Int, allocator := context.allocator) -> (rho: DIGIT, err: Error) { + assert_if_nil(n); + internal_clear_if_uninitialized(n, allocator) or_return; + + return #force_inline internal_int_montgomery_setup(n); +} + /* Returns the number of Rabin-Miller trials needed for a given bit size. */ |