rfc:rng_extension

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
rfc:rng_extension [2021/07/07 13:52]
zeriyoshi fix interface and drop C implementation stub, they're currently not fixed.
rfc:rng_extension [2021/09/03 14:28] (current)
zeriyoshi
Line 1: Line 1:
-====== PHP RFC: Add Random Extension ====== +====== PHP RFC: Random Extension 3.0 ====== 
-  * Version: 2.2 +  * Version: 3.0 
-  * Date: 2021-05-18+  * Date: 2021-09-02
   * Author: Go Kudo <zeriyoshi@gmail.com>   * Author: Go Kudo <zeriyoshi@gmail.com>
   * Status: Under Discussion   * Status: Under Discussion
-  * Implementation: https://github.com/php/php-src/pull/7079+  * Implementation: https://github.com/php/php-src/pull/7453
   * First Published at: http://wiki.php.net/rfc/object_scope_prng   * First Published at: http://wiki.php.net/rfc/object_scope_prng
  
 ===== Introduction ===== ===== Introduction =====
-PHP is currently having problems with RNG reproducibility. 
  
-PHP'RNG has been unified into an implementation using the Mersenne twister, with the rand() and srand() functions becoming aliases for mt_rand() and mt_srand() respectively in PHP 7.1.+Currently, PHP'random number implementation suffers from several problems.
  
-Butthese functions still store the state in the global state of PHP and are not easily reproducible. Look at the following example.+The first is that there are many different implementations. Historically, the random number implementations have been separated into lcg.c, rand.c, mt_rand.c random.c respectively, and the header file dependencies are complex. 
 + 
 +Second, the pseudo-random number generator makes use of global state. If a random number is consumed at an unexpected time, the reproducibility of the result may be lost. Look at the following example.
  
 <code php> <code php>
Line 27: Line 28:
 </code> </code>
  
-As mentioned above, the reproducibility of random numbers can easily be lost if additional processing is added later.+Reproducibility of random numbers can easily be lost if additional code is added later.
  
 In addition, the fiber extension was introduced in PHP 8.1. This makes it more difficult to keep track of the execution order. However, this problem has existed since the introduced of Generator. In addition, the fiber extension was introduced in PHP 8.1. This makes it more difficult to keep track of the execution order. However, this problem has existed since the introduced of Generator.
Line 43: Line 44:
  
 ===== Proposal ===== ===== Proposal =====
-Implement and bundled Random extension into PHP. 
  
-The pseudo-implementation for the whole extension is as follows:+Clean up the implementation, separate out the random number related functions as Random extension, and add an object scoped API.
  
-<code php> +All of the following functions will be moved to the newly created Random extension.
-<?php+
  
-namespace Random +  * lcg_value() 
-{ +  * srand() 
-    interface NumberGenerator +  * rand() 
-    { +  * mt_srand() 
-        public function generate(): int; +  * mt_rand() 
-    } +  * random_int() 
-}+  * random_bytes()
  
-namespace Random\NumberGenerator +At the same time, the following internal APIs will also be relocated. If you want to use them, you can simply include ext/random/random.h.
-+
-    class XorShift128Plus implements Random\NumberGenerator +
-    { +
-        public function __construct(?int $seed = null) {} +
-        public function generate(): int {} +
-        public function __serialize(): array {} +
-        public function __unserialize(array $data): void {} +
-    }+
  
-    class MT19937 implements Random\NumberGenerator +  * php_random_int_throw() 
-    { +  * php_random_int_silent() 
-        public function __construct(?int $seed = null{} +  * php_combined_lcg() 
-        public function generate(): int {} +  * php_mt_srand() 
-        public function __serialize(): array {} +  * php_mt_rand() 
-        public function __unserialize(array $data): void {} +  * php_mt_rand_range() 
-    }+  * php_mt_rand_common() 
 +  * php_srand() 
 +  * php_rand() 
 +  * php_random_bytes() 
 +  * php_random_int()
  
-    class Secure implements Random\NumberGenerator +The following PHP constants will now be provided by the Random extension
-    { +
-        public function __construct() {} +
-        public function generate(): int {} +
-    } +
-}+
  
-namespace +  * MT_RAND_MT19937 
-+  * MT_RAND_PHP
-    final class Random +
-    { +
-        private Random\NumberGenerator $rng;+
  
-        public function __construct(?Random\NumberGenerator $rng = null) {} +To solve the scope problem, the following classes will be added 
-        public function getNumberGenerator(): Random\NumberGenerator {} + 
-        public function getInt(int $min, int $max): int {} +  * Random class 
-        public function getBytes(int $length): string {} +  * Random\NumberGenrator abstract class 
-        public function shuffleArray(array $array): array {} +  * Random\NumberGenerator\XorShift128Plus class 
-        public function shuffleString(string $string): string {} +  Random\NumberGenerator\MT19937 class 
-        public function __serialize(): array {} +  * Random\NumberGenerator\Secure class 
-        public function __unserialize(array $data): void {} + 
-    } +The Random class is a utility class that provides functionality using random numbers. It provides the following methods, but does not provide an alternative to array_rand because it is too complex. 
-}+ 
 +  getInt() 
 +  getBytes() 
 +  shuffleArray() 
 +  shuffleString()
  
 +This class can be used in the following way.
 +
 +<code php>
 +// functions
 +mt_srand(1234);
 +mt_rand(); // generate random number
 +mt_rand(1, 10); // generate random number in range
 +str_shuffle("foobar"); // shuffle string
 +$arr = range(1, 10);
 +shuffle($arr); // shuffle array items (pass by reference)
 +
 +// object
 +$mt = new Random\NumberGenerator\MT19937(1234);
 +$mt->generate(); // generate random number 
 +$random = new Random($mt);
 +$random->getInt(1, 10); // generate random number in range
 +$random->shuffleString("foobar"); // shuffle string
 +$random->shuffleArray(range(1, 10)); // shuffle array items (pass by value)
 </code> </code>
  
-Each RNG is implemented as a class in the Random\NumberGenerator namespace. They all implement the Random\NumberGenerator interface.+The Random class accepts an instance that inherits from Random\NumberGenerator as a constructor argument.
  
-The bundled RNGs are as follows:+This class is final and cannot be cloned, but it can be serialized. 
 +This is to prevent $rng from being copied by reference to a property and causing unintended behavior.
  
-  * Random\NumberGenerator\XorShift128Plus: 64-bit, reproducible, PRNG. +The serializability depends on the serializability of the contained $rng.
-  * Random\NumberGenerator\MT19937: 32-bit, reproducible, PRNG, compatible mt_srand() / mt_rand(). +
-  * Random\NumberGenerator\Secure: 64-bit, non-reproducible, CSPRNG, uses php_random_bytes() internally.+
  
-Random class use a XorShift128+ by default. It can generate 64-bit values, is used by major browsers, and is fast and reliable.  +<code php> 
-However, when used XorShift128+ in a 32-bit environment, the upper 32 bits are always truncated. This means that compatibility cannot be maintained between platforms, but this is not a problem since most platforms running PHP today are 64-bit and MT19937 can be used explicitly if compatibility is required.+final class Random 
 +
 +    private Random\NumberGenerator $randomNumberGenerator;
  
-Note that (new Random\NumberGenerator\MT19937($seed))->generate() requires an additional bit shift to get a result equivalent to mt_rand(). mt_rand() implicitly did the bit-shifting internally, but there was no obvious reason for this.+    public function __construct(?Random\NumberGenerator $randomNumberGenerator = null) {} 
 +    public function getNumberGenerator(): Random\NumberGenerator {} 
 +    public function getInt(int $min, int $max): int {} 
 +    public function getBytes(int $length): string {} 
 +    public function shuffleArray(array $array): array {} 
 +    public function shuffleString(string $string): string {}
  
-Secure is practically equivalent to random_int() and random_bytes(), This is useful when secure array or string shuffling is required.+    public function __serialize(): array {} 
 +    public function __unserialize(array $data): void {} 
 +
 +</code>
  
-This class also supports RNGs defined in userland. It can be used by passing an instance of a class that implements the Random\NumberGenerator interface provided at the same time as the first argument.This is useful for unit testing or when you want to use fixed number.+The Random\NumberGenerator abstract class has single abstract method called generate()
  
 <code php> <code php>
 +namespace Random;
  
-class UserDefinedRNG implements Random\NumberGenerator+abstract class NumberGenerator 
 +
 +        abstract public function generate(): int {} 
 +
 +</code> 
 + 
 +By defining a class that extends Random\NumberGenerator, the user can use their own random number generator. With the introduction of JIT in PHP 8.0, this can generate random numbers at a realistic speed. 
 + 
 +<code php> 
 +class UserDefinedRNG extends Random\NumberGenerator
 { {
     protected int $current = 0;     protected int $current = 0;
-    +
     public function generate(): int     public function generate(): int
     {     {
Line 140: Line 168:
 </code> </code>
  
-Also, as with MT, various alternative APIs using Random class will be provided.+It is also useful when you want to use a random number sequence with a fixed resultsuch as in testing.
  
-The Random class can be serialized using the standard PHP serialization mechanism. But, if the $rng member is not serializableit will throws Exception.+The Random class creates and uses an instance of the default random number generator, Random\NumberGenerator\XorShift128Plus, if the constructor argument is omitted. 
 + 
 +XorShift128Plus is an efficienthigh-quality algorithm used in modern browsers and other applications. This algorithm is capable of generating a wider range of random numbers in a 64-bit environment. In a 32-bit environment, the range beyond zend_long will simply be truncated. This indicates incompatibility between environments, but is acceptable for real-world use. 
 + 
 +The Random\NumberGenerator\MT19937 class, which implements the MT19937 Mersenne twister, is also provided for backward compatibility or when a higher period is required. However, a 1-bit right shift is required to obtain exactly the same result as mt_rand(), as shown below. This is due to historical reasons.
  
 <code php> <code php>
-// serialize +$seed 1234;
-$foo new Random(new Random\Numbergenerator\XorShift128Plus()); +
-for ($i = 0; $i < 10; $i++) { $foo->getInt(PHP_INT_MIN, PHP_INT_MAX);+
-var_dump(unserialize(serialize($foo))->getInt(PHP_INT_MIN, PHP_INT_MAX) === $foo->getInt(PHP_INT_MIN, PHP_INT_MAX)); // true+
  
-// can't serialize +$mt = new Random\NumberGenerator\MT19937($seed); 
-$foo new Random(new Random\Numbergenerator\Secure()); +mt_srand($seed); 
-for ($i = 0; $i < 10; $i++) { $foo->getInt(PHP_INT_MIN, PHP_INT_MAX); } +var_dump(mt_rand() === ($mt->generate() >> 1)); // true
-var_dump(unserialize(serialize($foo))->getInt(PHP_INT_MIN, PHP_INT_MAX) === $foo->getInt(PHP_INT_MIN, PHP_INT_MAX)); // throws Exception:  Serialization of CLASS is not allowed.+
 </code> </code>
  
-It is not possible to clone the Random class. it always throws Error (Error: Trying to clone an uncloneable object of class Random). This is because the standard PHP clone method copies the members by reference when cloning. This will be an unintended behavior for most users. Instead, you can use the getNumberGenerator() method to retrieve the internal RNG instance. The RNG instance can be cloned.+The following NumberGenerator class supports serializationSecure is not serializable because it uses random_bytes internally and has no state.
  
-<code php> +  * Random\NumberGenerator\XorShift128Plus 
-$foo = new Random();+  * Random\NumberGenerator\MT19937 
 +  Random\NumberGenerator extends user-defined classes.
  
-// can't direct clone +Also, a new internal API will be implemented.
-// $bar = clone $foo;+
  
-// safe +  * php_random_ng_next() 
-$bar = new Random(clone $foo->getNumberGenerator()); +  * php_random_ng_range(
-</code>+  * php_random_ng_array_data_shuffle() 
 +  * php_random_ng_string_shuffle()
  
-Using this feature, the first example can be rewritten as follows:+A Stub showing these implementations can be found on the Pull-Request. It's probably easier to understand if you look at it.
  
-<code php> +  * [[https://github.com/php/php-src/blob/7a4ef6ccfbf4a2cd48a4f261f2911ebb7b057d46/ext/random/random.stub.php|random.stub.php]]
-echo foo(1234, function ()void {}) . PHP_EOL; // Result: 1480009472 +
-echo foo(1234, function (): void { mt_rand(); }) PHP_EOL; // Result: 1480009472 +
- +
-function foo(int $seed, callable $bar): int { +
-    $numberGenerator = new Random\NumberGenerator\MT19937($seed); +
-    $result = ($numberGenerator->generate() >> 1); // requires bit-shift for compatibility. +
-    $bar(); +
-    $result += ($numberGenerator->generate() >> 1); // requires bit-shift for compatibility. +
-    return $result; +
-+
-</code>+
  
 ===== Future Scope ===== ===== Future Scope =====
  
-This RFC will be the basis for making PHP RNGs safe in the future.+This proposal is just a first step to improve the situation of PHP's random number implementation.
  
-By first accepted this RFCPHP gets a random number in the local scope.+If this proposal is approvedI will then propose the following changes
  
-The Random class can also be used when new features are implemented that use random numbers. This has the effect of discouraging more implementations from using random numbers that depend on the global scope.+  * Replace the state of the existing implementation with php_random_ng. 
 +  * Changes random source to php_random_int() a shuffle(), str_shuffle(), and array_rand() . 
 +  * Deprecate srand() and mt_srand() (step by step)
  
-More in the future, we can consider doing away with functions such as mt_srand(). These functions are simple and convenient, but they may unintentionally create implementations that depend on global scope.+===== Backward Incompatible Changes =====
  
-===== Backward Incompatible Changes ===== +The code that includes the following header file needs to be changed to ext/random/random.h 
-The following class name will no longer be available:+ 
 +  * ext/standard/lcg.h 
 +  * ext/standard/rand.h 
 +  * ext/standard/mt_rand.h 
 +  * ext/standard/random.h 
 + 
 +The following class names have been reserved and will no longer be available
  
   * "Random"   * "Random"
Line 203: Line 229:
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
-8.1+8.2
  
 ===== RFC Impact ===== ===== RFC Impact =====
Line 233: Line 259:
  
 ===== Patches and Tests ===== ===== Patches and Tests =====
-  * https://github.com/php/php-src/pull/7079+  * https://github.com/php/php-src/pull/7453
rfc/rng_extension.1625665969.txt.gz · Last modified: 2021/07/07 13:52 by zeriyoshi