From 206b9f74779cef53e3e56188f1cc41ab752f4e86 Mon Sep 17 00:00:00 2001 From: "Yukihiro \"Matz\" Matsumoto" Date: Thu, 21 May 2026 17:07:51 +0900 Subject: [PATCH] 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 --- mrbgems/mruby-bigint/core/bigint.c | 14 +++++++++++--- mrbgems/mruby-bigint/test/bigint.rb | 14 ++++++++++++++ 2 files changed, 25 insertions(+), 3 deletions(-) diff --git a/mrbgems/mruby-bigint/core/bigint.c b/mrbgems/mruby-bigint/core/bigint.c index 5c64c7d87..3ffce860d 100644 --- a/mrbgems/mruby-bigint/core/bigint.c +++ b/mrbgems/mruby-bigint/core/bigint.c @@ -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); diff --git a/mrbgems/mruby-bigint/test/bigint.rb b/mrbgems/mruby-bigint/test/bigint.rb index 289e93da8..d05043c36 100644 --- a/mrbgems/mruby-bigint/test/bigint.rb +++ b/mrbgems/mruby-bigint/test/bigint.rb @@ -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