mirror of
https://github.com/mruby/mruby
synced 2026-06-08 16:11:16 +00:00
mruby-bigint: pre-reduce base in mpz_powm_montgomery to satisfy REDC
REDC's input T must satisfy T < R*N; mpz_powm_montgomery() computed T = base * R^2 without first reducing base modulo n. When base >= n, T exceeds R*N and REDC silently truncates upper limbs, producing a wrong base_mont and ultimately a wrong result. Use mpz_mmod (general division path) to pre-reduce base mod n before the multiplication by R^2. Visible effect: (2**160).pow(2, (2**40)+1) returned 0 instead of 1. A separate pre-existing issue in mpz_mod's Barrett path (broken precondition check) means that path could return a wrong reduction for large operands; mmod sidesteps that path entirely. Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5250,12 +5250,19 @@ mpz_powm_montgomery(mpz_ctx_t *ctx, mpz_t *result,
|
||||
mpz_init(ctx, &one_mont);
|
||||
mpz_montgomery_reduce(ctx, &one_mont, &R2, n, rho);
|
||||
|
||||
/* Convert base to Montgomery form: base_mont = base * R mod n = REDC(base * R^2) */
|
||||
mpz_t base_mont, temp;
|
||||
/* Convert base to Montgomery form: base_mont = base * R mod n = REDC(base * R^2).
|
||||
* REDC requires its input T to satisfy T < R*N. If `base` is not already
|
||||
* reduced (e.g. base >= n), `base * R^2` can exceed R*N and REDC produces
|
||||
* a wrong result. Pre-reduce base modulo n via mpz_mmod (the general
|
||||
* division path) -- both operands are non-negative here so this is
|
||||
* semantically equivalent to mpz_mod. */
|
||||
mpz_t base_mont, base_reduced, temp;
|
||||
mpz_init(ctx, &base_mont);
|
||||
mpz_init(ctx, &base_reduced);
|
||||
mpz_init_temp(ctx, &temp, n->sz * 4);
|
||||
|
||||
mpz_mul(ctx, &temp, (mpz_t*)base, &R2);
|
||||
mpz_mmod(ctx, &base_reduced, (mpz_t*)base, (mpz_t*)n);
|
||||
mpz_mul(ctx, &temp, &base_reduced, &R2);
|
||||
mpz_montgomery_reduce(ctx, &base_mont, &temp, n, rho);
|
||||
|
||||
/* Initialize accumulator to 1 in Montgomery form */
|
||||
@@ -5287,6 +5294,7 @@ mpz_powm_montgomery(mpz_ctx_t *ctx, mpz_t *result,
|
||||
mpz_clear(ctx, &R2);
|
||||
mpz_clear(ctx, &one_mont);
|
||||
mpz_clear(ctx, &base_mont);
|
||||
mpz_clear(ctx, &base_reduced);
|
||||
mpz_clear(ctx, &temp);
|
||||
mpz_clear(ctx, &acc);
|
||||
pool_restore(ctx, pool_state);
|
||||
|
||||
@@ -150,6 +150,20 @@ assert 'Bigint pow' do
|
||||
# assert_equal(-1041439304, n.pow(n, -1234567890))
|
||||
end
|
||||
|
||||
assert 'Bigint Integer#pow(e, m) - Montgomery path' do
|
||||
# Regression: mpz_powm_montgomery() failed to pre-reduce base mod n,
|
||||
# producing wrong results when base >= n. Also trim() must restore
|
||||
# the canonical sn=0 when sz becomes 0, otherwise an inconsistent
|
||||
# zero bignum (sn!=0, sz=0) propagates through the squaring loop.
|
||||
m = (2**40) + 1
|
||||
assert_equal 1, (2**160).pow(2, m)
|
||||
assert_equal 1, (2**320).pow(2, m)
|
||||
assert_equal 8, ((2**160) + 1).pow(3, m)
|
||||
m2 = (2**100) + 3
|
||||
assert_equal (3**500) % m2, (3**500).pow(1, m2)
|
||||
assert_equal ((5**300) ** 7) % m2, (5**300).pow(7, m2)
|
||||
end
|
||||
|
||||
assert 'Bigint abs' do
|
||||
n = 1<<65
|
||||
assert_equal 36893488147419103232, n.abs
|
||||
|
||||
Reference in New Issue
Block a user