Commit 081106ea44f for php.net
commit 081106ea44f9e93df66291191d58c30828bea7aa
Author: Weilin Du <weilindu@php.net>
Date: Tue Jul 28 21:12:10 2026 +0800
ext/gmp: Add gmp_powm_sec() (#22852)
Expose mpz_powm_sec() to the userland, namely gmp_powm_sec().
diff --git a/NEWS b/NEWS
index 48864929c22..65bd87faba8 100644
--- a/NEWS
+++ b/NEWS
@@ -20,6 +20,7 @@ PHP NEWS
. Made php-cli functionality available in embed builds. (henderkes)
- GMP:
+ . Added gmp_powm_sec(). (Weilin Du)
. Added gmp_prevprime(). (Weilin Du, David Carlier)
. Fixed GMP power and shift operators to reject GMP right operands outside
the unsigned long range instead of silently truncating them. (Weilin Du)
diff --git a/UPGRADING b/UPGRADING
index 3d9cfdc6aeb..7c9473d07b7 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -272,6 +272,8 @@ PHP 8.6 UPGRADE NOTES
. finfo_file() now works with remote streams.
- GMP:
+ . Added gmp_powm_sec() for side-channel quiet modular exponentiation.
+ Requires GNU MP 5.0.0 or later.
. Added gmp_prevprime() to get the largest prime smaller than the given
number. A ValueError is thrown if no such prime exists. This function is
available only when PHP is built against GNU MP 6.3.0 or later; it is not
@@ -428,6 +430,7 @@ PHP 8.6 UPGRADE NOTES
========================================
- GMP:
+ . gmp_powm_sec()
. gmp_prevprime()
- Intl:
diff --git a/ext/gmp/config.m4 b/ext/gmp/config.m4
index c4ffc2d42d4..f6f2a4a0e9a 100644
--- a/ext/gmp/config.m4
+++ b/ext/gmp/config.m4
@@ -22,7 +22,7 @@ if test "$PHP_GMP" != "no"; then
LIBS="$LIBS $GMP_LIBS"
gmp_check=no
AC_CHECK_HEADER([gmp.h], [AC_CHECK_FUNC([__gmpz_rootrem], [gmp_check=yes])])
- AC_CHECK_FUNCS([__gmpz_prevprime])
+ AC_CHECK_FUNCS([__gmpz_powm_sec __gmpz_prevprime])
CFLAGS=$CFLAGS_SAVED
LIBS=$LIBS_SAVED
diff --git a/ext/gmp/config.w32 b/ext/gmp/config.w32
index d83048ebe9c..fc052440417 100644
--- a/ext/gmp/config.w32
+++ b/ext/gmp/config.w32
@@ -5,6 +5,9 @@ ARG_WITH("gmp", "Include GNU MP support.", "no");
if (PHP_GMP != "no") {
if (CHECK_LIB("mpir_a.lib", "gmp", PHP_GMP) &&
CHECK_HEADER("gmp.h", "CFLAGS_GMP", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) {
+ if (GREP_HEADER("gmp.h", "mpz_powm_sec", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) {
+ AC_DEFINE('HAVE___GMPZ_POWM_SEC', 1, "Define to 1 if GMP has the 'mpz_powm_sec' function.");
+ }
if (GREP_HEADER("gmp.h", "mpz_prevprime", PHP_GMP + ";" + PHP_PHP_BUILD + "\\include\\mpir")) {
AC_DEFINE('HAVE___GMPZ_PREVPRIME', 1, "Define to 1 if GMP has the 'mpz_prevprime' function.");
}
diff --git a/ext/gmp/gmp.c b/ext/gmp/gmp.c
index 9ade8eade21..bf0a77666b6 100644
--- a/ext/gmp/gmp.c
+++ b/ext/gmp/gmp.c
@@ -1217,6 +1217,39 @@ ZEND_FUNCTION(gmp_powm)
}
/* }}} */
+#ifdef HAVE___GMPZ_POWM_SEC
+/* {{{ Raise base to power exp and take result modulo mod using a side-channel quiet algorithm */
+ZEND_FUNCTION(gmp_powm_sec)
+{
+ mpz_ptr gmpnum_base, gmpnum_exp, gmpnum_mod, gmpnum_result;
+
+ ZEND_PARSE_PARAMETERS_START(3, 3)
+ GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_base)
+ GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_exp)
+ GMP_Z_PARAM_INTO_MPZ_PTR(gmpnum_mod)
+ ZEND_PARSE_PARAMETERS_END();
+
+ if (mpz_sgn(gmpnum_exp) <= 0) {
+ zend_argument_value_error(2, "must be greater than 0");
+ RETURN_THROWS();
+ }
+
+ if (UNEXPECTED(!mpz_odd_p(gmpnum_mod))) {
+ /* Zero is an even modulus, but report it like gmp_powm() does. */
+ if (!mpz_cmp_ui(gmpnum_mod, 0)) {
+ zend_argument_error(zend_ce_division_by_zero_error, 3, "Modulo by zero");
+ } else {
+ zend_argument_value_error(3, "must be odd");
+ }
+ RETURN_THROWS();
+ }
+
+ INIT_GMP_RETVAL(gmpnum_result);
+ mpz_powm_sec(gmpnum_result, gmpnum_base, gmpnum_exp, gmpnum_mod);
+}
+/* }}} */
+#endif
+
/* {{{ Takes integer part of square root of a */
ZEND_FUNCTION(gmp_sqrt)
{
diff --git a/ext/gmp/gmp.stub.php b/ext/gmp/gmp.stub.php
index d3c603310a4..f2ea1548174 100644
--- a/ext/gmp/gmp.stub.php
+++ b/ext/gmp/gmp.stub.php
@@ -125,6 +125,10 @@ function gmp_pow(GMP|int|string $num, int $exponent): GMP {}
function gmp_powm(GMP|int|string $num, GMP|int|string $exponent, GMP|int|string $modulus): GMP {}
+#ifdef HAVE___GMPZ_POWM_SEC
+function gmp_powm_sec(GMP|int|string $num, GMP|int|string $exponent, GMP|int|string $modulus): GMP {}
+#endif
+
function gmp_perfect_square(GMP|int|string $num): bool {}
function gmp_perfect_power(GMP|int|string $num): bool {}
diff --git a/ext/gmp/gmp_arginfo.h b/ext/gmp/gmp_arginfo.h
index 957fdc8d776..60eec94bb5c 100644
Binary files a/ext/gmp/gmp_arginfo.h and b/ext/gmp/gmp_arginfo.h differ
diff --git a/ext/gmp/tests/bug80560.phpt b/ext/gmp/tests/bug80560.phpt
index c5ccc050070..85f4dff96be 100644
--- a/ext/gmp/tests/bug80560.phpt
+++ b/ext/gmp/tests/bug80560.phpt
@@ -63,6 +63,9 @@
$functions3 = [
'gmp_powm',
];
+if (function_exists('gmp_powm_sec')) {
+ $functions3[] = 'gmp_powm_sec';
+}
echo 'Explicit base with gmp_init:', \PHP_EOL;
echo 'Hexadecimal', \PHP_EOL;
diff --git a/ext/gmp/tests/gmp_powm_sec.phpt b/ext/gmp/tests/gmp_powm_sec.phpt
new file mode 100644
index 00000000000..2c66ff25a5e
--- /dev/null
+++ b/ext/gmp/tests/gmp_powm_sec.phpt
@@ -0,0 +1,46 @@
+--TEST--
+gmp_powm_sec()
+--EXTENSIONS--
+gmp
+--SKIPIF--
+<?php
+if (!function_exists('gmp_powm_sec')) {
+ die('skip gmp_powm_sec() is not available');
+}
+?>
+--FILE--
+<?php
+
+var_dump(gmp_strval(gmp_powm_sec(4, 13, 497)));
+var_dump(gmp_strval(gmp_powm_sec(gmp_init(7), gmp_init(3), gmp_init(13))));
+
+foreach ([0, -1] as $exp) {
+ try {
+ var_dump(gmp_powm_sec(4, $exp, 497));
+ } catch (\ValueError $e) {
+ echo $e::class, ": ", $e->getMessage(), \PHP_EOL;
+ }
+}
+
+try {
+ var_dump(gmp_powm_sec(4, 13, 0));
+} catch (\DivisionByZeroError $e) {
+ echo $e::class, ": ", $e->getMessage(), \PHP_EOL;
+}
+
+try {
+ var_dump(gmp_powm_sec(4, 13, 496));
+} catch (\ValueError $e) {
+ echo $e::class, ": ", $e->getMessage(), \PHP_EOL;
+}
+
+echo "Done\n";
+?>
+--EXPECT--
+string(3) "445"
+string(1) "5"
+ValueError: gmp_powm_sec(): Argument #2 ($exponent) must be greater than 0
+ValueError: gmp_powm_sec(): Argument #2 ($exponent) must be greater than 0
+DivisionByZeroError: gmp_powm_sec(): Argument #3 ($modulus) Modulo by zero
+ValueError: gmp_powm_sec(): Argument #3 ($modulus) must be odd
+Done