[php-src] master: ext/gmp: Add gmp_powm_sec() (#22852)

Weilin Du via GitHub <[email protected]> Tue, 28 Jul 2026 13:12:13 +0000
Newsgroups gmane.comp.php.cvs.general
Message-ID <[email protected]>
Author: Weilin Du (LamentXU123)
Committer: GitHub (web-flow)
Pusher: LamentXU123
Date: 2026-07-28T21:12:10+08:00

Commit: https://github.com/php/php-src/commit/081106ea44f9e93df66291191d58c30828bea7aa
Raw diff: https://github.com/php/php-src/commit/081106ea44f9e93df66291191d58c30828bea7aa.diff

ext/gmp: Add gmp_powm_sec() (#22852)

Expose mpz_powm_sec() to the userland, namely gmp_powm_sec().

Changed paths:
  A  ext/gmp/tests/gmp_powm_sec.phpt
  M  NEWS
  M  UPGRADING
  M  ext/gmp/config.m4
  M  ext/gmp/config.w32
  M  ext/gmp/gmp.c
  M  ext/gmp/gmp.stub.php
  M  ext/gmp/gmp_arginfo.h
  M  ext/gmp/tests/bug80560.phpt


Diff:

diff --git a/NEWS b/NEWS
index 48864929c225..65bd87faba82 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 3d9cfdc6aeb9..7c9473d07b7d 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 c4ffc2d42d4d..f6f2a4a0e9a3 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 d83048ebe9c6..fc052440417c 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 9ade8eade215..bf0a77666b6f 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 d3c603310a4a..f2ea15481748 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 957fdc8d7765..60eec94bb5cf 100644
--- a/ext/gmp/gmp_arginfo.h
+++ b/ext/gmp/gmp_arginfo.h
@@ -1,5 +1,5 @@
 /* This is a generated file, edit gmp.stub.php instead.
- * Stub hash: 9d651cc4ba238a496ebe8302fe3e0f985d48d769 */
+ * Stub hash: 57d016aed930bb41ff4357917e3ae8abd612e973 */
 
 ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_init, 0, 1, GMP, 0)
 	ZEND_ARG_TYPE_MASK(0, num, MAY_BE_LONG|MAY_BE_STRING, NULL)
@@ -91,6 +91,14 @@ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_powm, 0, 3, GMP, 0)
 	ZEND_ARG_OBJ_TYPE_MASK(0, modulus, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL)
 ZEND_END_ARG_INFO()
 
+#if defined(HAVE___GMPZ_POWM_SEC)
+ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_gmp_powm_sec, 0, 3, GMP, 0)
+	ZEND_ARG_OBJ_TYPE_MASK(0, num, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL)
+	ZEND_ARG_OBJ_TYPE_MASK(0, exponent, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL)
+	ZEND_ARG_OBJ_TYPE_MASK(0, modulus, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL)
+ZEND_END_ARG_INFO()
+#endif
+
 ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_gmp_perfect_square, 0, 1, _IS_BOOL, 0)
 	ZEND_ARG_OBJ_TYPE_MASK(0, num, GMP, MAY_BE_LONG|MAY_BE_STRING, NULL)
 ZEND_END_ARG_INFO()
@@ -224,6 +232,9 @@ ZEND_FUNCTION(gmp_root);
 ZEND_FUNCTION(gmp_rootrem);
 ZEND_FUNCTION(gmp_pow);
 ZEND_FUNCTION(gmp_powm);
+#if defined(HAVE___GMPZ_POWM_SEC)
+ZEND_FUNCTION(gmp_powm_sec);
+#endif
 ZEND_FUNCTION(gmp_perfect_square);
 ZEND_FUNCTION(gmp_perfect_power);
 ZEND_FUNCTION(gmp_prob_prime);
@@ -283,6 +294,9 @@ static const zend_function_entry ext_functions[] = {
 	ZEND_FE(gmp_rootrem, arginfo_gmp_rootrem)
 	ZEND_FE(gmp_pow, arginfo_gmp_pow)
 	ZEND_FE(gmp_powm, arginfo_gmp_powm)
+#if defined(HAVE___GMPZ_POWM_SEC)
+	ZEND_FE(gmp_powm_sec, arginfo_gmp_powm_sec)
+#endif
 	ZEND_FE(gmp_perfect_square, arginfo_gmp_perfect_square)
 	ZEND_FE(gmp_perfect_power, arginfo_gmp_perfect_power)
 	ZEND_FE(gmp_prob_prime, arginfo_gmp_prob_prime)
diff --git a/ext/gmp/tests/bug80560.phpt b/ext/gmp/tests/bug80560.phpt
index c5ccc050070c..85f4dff96be4 100644
--- a/ext/gmp/tests/bug80560.phpt
+++ b/ext/gmp/tests/bug80560.phpt
@@ -63,6 +63,9 @@ $functions2 = [
 $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 000000000000..2c66ff25a5e5
--- /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