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