mirror of
https://github.com/danog/tgseclib.git
synced 2024-12-05 13:39:22 +01:00
1039 lines
32 KiB
PHP
1039 lines
32 KiB
PHP
<?php
|
|
|
|
/**
|
|
* Base BigInteger Engine
|
|
*
|
|
* PHP version 5 and 7
|
|
*
|
|
* @category Math
|
|
* @package BigInteger
|
|
* @author Jim Wigginton <terrafrost@php.net>
|
|
* @copyright 2017 Jim Wigginton
|
|
* @license http://www.opensource.org/licenses/mit-license.html MIT License
|
|
* @link http://pear.php.net/package/Math_BigInteger
|
|
*/
|
|
|
|
namespace phpseclib\Math\BigInteger\Engines;
|
|
|
|
use ParagonIE\ConstantTime\Hex;
|
|
use phpseclib\Exception\BadConfigurationException;
|
|
use phpseclib\Crypt\Random;
|
|
|
|
/**
|
|
* Base Engine.
|
|
*
|
|
* @package Engine
|
|
* @author Jim Wigginton <terrafrost@php.net>
|
|
* @access public
|
|
*/
|
|
abstract class Engine implements \Serializable
|
|
{
|
|
/**
|
|
* Holds the BigInteger's value
|
|
*
|
|
* @var mixed
|
|
*/
|
|
protected $value;
|
|
|
|
/**
|
|
* Holds the BigInteger's sign
|
|
*
|
|
* @var bool
|
|
*/
|
|
protected $is_negative;
|
|
|
|
/**
|
|
* Precision
|
|
*
|
|
* @see static::setPrecision()
|
|
*/
|
|
protected $precision = -1;
|
|
|
|
/**
|
|
* Precision Bitmask
|
|
*
|
|
* @see static::setPrecision()
|
|
*/
|
|
protected $bitmask = false;
|
|
|
|
/**
|
|
* Default constructor
|
|
*
|
|
* @param $x integer Base-10 number or base-$base number if $base set.
|
|
* @param int $base
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public function __construct($x, $base)
|
|
{
|
|
if (!isset(static::$primes)) {
|
|
static::$primes = [
|
|
3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59,
|
|
61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137,
|
|
139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
|
|
229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313,
|
|
317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419,
|
|
421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
|
|
521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617,
|
|
619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727,
|
|
733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
|
|
839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947,
|
|
953, 967, 971, 977, 983, 991, 997
|
|
];
|
|
static::$zero = new static(0);
|
|
static::$one = new static(1);
|
|
static::$two = new static(2);
|
|
}
|
|
|
|
// '0' counts as empty() but when the base is 256 '0' is equal to ord('0') or 48
|
|
// '0' is the only value like this per http://php.net/empty
|
|
if (empty($x) && (abs($base) != 256 || $x !== '0')) {
|
|
return;
|
|
}
|
|
|
|
switch ($base) {
|
|
case -256:
|
|
case 256:
|
|
if ($base == -256 && (ord($x[0]) & 0x80)) {
|
|
$this->value = ~$x;
|
|
$this->is_negative = true;
|
|
} else {
|
|
$this->value = $x;
|
|
$this->is_negative = false;
|
|
}
|
|
|
|
static::initialize($base);
|
|
|
|
if ($this->is_negative) {
|
|
$temp = $this->add(new static('-1'));
|
|
$this->value = $temp->value;
|
|
}
|
|
break;
|
|
case -16:
|
|
case 16:
|
|
if ($base > 0 && $x[0] == '-') {
|
|
$this->is_negative = true;
|
|
$x = substr($x, 1);
|
|
}
|
|
|
|
$x = preg_replace('#^(?:0x)?([A-Fa-f0-9]*).*#', '$1', $x);
|
|
|
|
$is_negative = false;
|
|
if ($base < 0 && hexdec($x[0]) >= 8) {
|
|
$this->is_negative = $is_negative = true;
|
|
$x = Hex::encode(~Hex::decode($x));
|
|
}
|
|
|
|
$this->value = $x;
|
|
static::initialize($base);
|
|
|
|
if ($is_negative) {
|
|
$temp = $this->add(new static('-1'));
|
|
$this->value = $temp->value;
|
|
}
|
|
break;
|
|
case -10:
|
|
case 10:
|
|
// (?<!^)(?:-).*: find any -'s that aren't at the beginning and then any characters that follow that
|
|
// (?<=^|-)0*: find any 0's that are preceded by the start of the string or by a - (ie. octals)
|
|
// [^-0-9].*: find any non-numeric characters and then any characters that follow that
|
|
$this->value = preg_replace('#(?<!^)(?:-).*|(?<=^|-)0*|[^-0-9].*#', '', $x);
|
|
static::initialize($base);
|
|
break;
|
|
case -2:
|
|
case 2:
|
|
if ($base > 0 && $x[0] == '-') {
|
|
$this->is_negative = true;
|
|
$x = substr($x, 1);
|
|
}
|
|
|
|
$x = preg_replace('#^([01]*).*#', '$1', $x);
|
|
$x = str_pad($x, strlen($x) + (3 * strlen($x)) % 4, 0, STR_PAD_LEFT);
|
|
|
|
$str = '0x';
|
|
while (strlen($x)) {
|
|
$part = substr($x, 0, 4);
|
|
$str.= dechex(bindec($part));
|
|
$x = substr($x, 4);
|
|
}
|
|
|
|
if ($this->is_negative) {
|
|
$str = '-' . $str;
|
|
}
|
|
|
|
$temp = new static($str, 8 * $base); // ie. either -16 or +16
|
|
$this->value = $temp->value;
|
|
$this->is_negative = $temp->is_negative;
|
|
|
|
break;
|
|
default:
|
|
// base not supported, so we'll let $this == 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets engine type.
|
|
*
|
|
* Throws an exception if the type is invalid
|
|
*
|
|
* @param string $engine
|
|
*/
|
|
public static function setModExpEngine($engine)
|
|
{
|
|
$fqengine = '\\phpseclib\\Math\\BigInteger\\Engines\\' . static::ENGINE_DIR . '\\' . $engine;
|
|
if (!class_exists($fqengine) || !method_exists($fqengine, 'isValidEngine')) {
|
|
throw new \InvalidArgumentException("$engine is not a valid engine");
|
|
}
|
|
if (!$fqengine::isValidEngine()) {
|
|
throw new BadConfigurationException("$engine is not setup correctly on this system");
|
|
}
|
|
static::$modexpEngine = $fqengine;
|
|
}
|
|
|
|
/**
|
|
* Converts a BigInteger to a byte string (eg. base-256).
|
|
*
|
|
* Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
|
|
* saved as two's compliment.
|
|
*
|
|
* @param bool $twos_compliment
|
|
* @return string
|
|
*/
|
|
protected function toBytesHelper()
|
|
{
|
|
$comparison = $this->compare(new static());
|
|
if ($comparison == 0) {
|
|
return $this->precision > 0 ? str_repeat(chr(0), ($this->precision + 1) >> 3) : '';
|
|
}
|
|
|
|
$temp = $comparison < 0 ? $this->add(new static(1)) : $this;
|
|
$bytes = $temp->toBytes();
|
|
|
|
if (empty($bytes)) { // eg. if the number we're trying to convert is -1
|
|
$bytes = chr(0);
|
|
}
|
|
|
|
if (ord($bytes[0]) & 0x80) {
|
|
$bytes = chr(0) . $bytes;
|
|
}
|
|
|
|
return $comparison < 0 ? ~$bytes : $bytes;
|
|
}
|
|
|
|
/**
|
|
* Converts a BigInteger to a hex string (eg. base-16).
|
|
*
|
|
* @param bool $twos_compliment
|
|
* @return string
|
|
*/
|
|
public function toHex($twos_compliment = false)
|
|
{
|
|
return Hex::encode($this->toBytes($twos_compliment));
|
|
}
|
|
|
|
/**
|
|
* Converts a BigInteger to a bit string (eg. base-2).
|
|
*
|
|
* Negative numbers are saved as positive numbers, unless $twos_compliment is set to true, at which point, they're
|
|
* saved as two's compliment.
|
|
*
|
|
* @param bool $twos_compliment
|
|
* @return string
|
|
*/
|
|
public function toBits($twos_compliment = false)
|
|
{
|
|
$hex = $this->toHex($twos_compliment);
|
|
$bits = '';
|
|
for ($i = strlen($hex) - 8, $start = strlen($hex) & 7; $i >= $start; $i-=8) {
|
|
$bits = str_pad(decbin(hexdec(substr($hex, $i, 8))), 32, '0', STR_PAD_LEFT) . $bits;
|
|
}
|
|
if ($start) { // hexdec('') == 0
|
|
$bits = str_pad(decbin(hexdec(substr($hex, 0, $start))), 8, '0', STR_PAD_LEFT) . $bits;
|
|
}
|
|
$result = $this->precision > 0 ? substr($bits, -$this->precision) : ltrim($bits, '0');
|
|
|
|
if ($twos_compliment && $this->compare(new static()) > 0 && $this->precision <= 0) {
|
|
return '0' . $result;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Calculates modular inverses.
|
|
*
|
|
* Say you have (30 mod 17 * x mod 17) mod 17 == 1. x can be found using modular inverses.
|
|
*
|
|
* @param \phpseclib\Math\BigInteger\Engines\Engine $n
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine|false
|
|
* @internal See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=21 HAC 14.64} for more information.
|
|
*/
|
|
protected function modInverseHelper(Engine $n)
|
|
{
|
|
// $x mod -$n == $x mod $n.
|
|
$n = $n->abs();
|
|
|
|
if ($this->compare(static::$zero) < 0) {
|
|
$temp = $this->abs();
|
|
$temp = $temp->modInverse($n);
|
|
return $this->normalize($n->subtract($temp));
|
|
}
|
|
|
|
extract($this->extendedGCD($n));
|
|
|
|
if (!$gcd->equals(static::$one)) {
|
|
return false;
|
|
}
|
|
|
|
$x = $x->compare(static::$zero) < 0 ? $x->add($n) : $x;
|
|
|
|
return $this->compare(static::$zero) < 0 ? $this->normalize($n->subtract($x)) : $this->normalize($x);
|
|
}
|
|
|
|
/**
|
|
* Serialize
|
|
*
|
|
* Will be called, automatically, when serialize() is called on a BigInteger object.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function serialize()
|
|
{
|
|
$val = ['hex' => $this->toHex(true)];
|
|
if ($this->precision > 0) {
|
|
$val['precision'] = $this->precision;
|
|
}
|
|
return serialize($val);
|
|
}
|
|
|
|
/**
|
|
* Serialize
|
|
*
|
|
* Will be called, automatically, when unserialize() is called on a BigInteger object.
|
|
*
|
|
* @param string $serialized
|
|
*/
|
|
public function unserialize($serialized)
|
|
{
|
|
$r = unserialize($serialized);
|
|
$temp = new static($r['hex'], -16);
|
|
$this->value = $temp->value;
|
|
$this->is_negative = $temp->is_negative;
|
|
if (isset($r['precision'])) {
|
|
// recalculate $this->bitmask
|
|
$this->setPrecision($r['precision']);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Converts a BigInteger to a base-10 number.
|
|
*
|
|
* @return string
|
|
*/
|
|
public function __toString()
|
|
{
|
|
return $this->toString();
|
|
}
|
|
|
|
/**
|
|
* __debugInfo() magic method
|
|
*
|
|
* Will be called, automatically, when print_r() or var_dump() are called
|
|
*/
|
|
public function __debugInfo()
|
|
{
|
|
return [
|
|
'value' => '0x' . $this->toHex(true),
|
|
'engine' => basename(static::class)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Set Precision
|
|
*
|
|
* Some bitwise operations give different results depending on the precision being used. Examples include left
|
|
* shift, not, and rotates.
|
|
*
|
|
* @param int $bits
|
|
*/
|
|
public function setPrecision($bits)
|
|
{
|
|
if ($bits < 1) {
|
|
$this->precision = -1;
|
|
$this->bitmask = false;
|
|
|
|
return;
|
|
}
|
|
$this->precision = $bits;
|
|
$this->bitmask = static::setBitmask($bits);
|
|
|
|
$temp = $this->normalize($this);
|
|
$this->value = $temp->value;
|
|
}
|
|
|
|
/**
|
|
* Get Precision
|
|
*
|
|
* Returns the precision if it exists, -1 if it doesn't
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getPrecision()
|
|
{
|
|
return $this->precision;
|
|
}
|
|
|
|
/**
|
|
* Set Bitmask
|
|
*
|
|
* @param int $bits
|
|
* @see self::setPrecision()
|
|
*/
|
|
protected static function setBitmask($bits)
|
|
{
|
|
return new static(chr((1 << ($bits & 0x7)) - 1) . str_repeat(chr(0xFF), $bits >> 3), 256);
|
|
}
|
|
|
|
/**
|
|
* Logical Not
|
|
*
|
|
* @return Engine|string
|
|
*/
|
|
public function bitwise_not()
|
|
{
|
|
// calculuate "not" without regard to $this->precision
|
|
// (will always result in a smaller number. ie. ~1 isn't 1111 1110 - it's 0)
|
|
$temp = $this->toBytes();
|
|
if ($temp == '') {
|
|
return $this->normalize(static::$zero);
|
|
}
|
|
$pre_msb = decbin(ord($temp[0]));
|
|
$temp = ~$temp;
|
|
$msb = decbin(ord($temp[0]));
|
|
if (strlen($msb) == 8) {
|
|
$msb = substr($msb, strpos($msb, '0'));
|
|
}
|
|
$temp[0] = chr(bindec($msb));
|
|
|
|
// see if we need to add extra leading 1's
|
|
$current_bits = strlen($pre_msb) + 8 * strlen($temp) - 8;
|
|
$new_bits = $this->precision - $current_bits;
|
|
if ($new_bits <= 0) {
|
|
return $this->normalize(new static($temp, 256));
|
|
}
|
|
|
|
// generate as many leading 1's as we need to.
|
|
$leading_ones = chr((1 << ($new_bits & 0x7)) - 1) . str_repeat(chr(0xFF), $new_bits >> 3);
|
|
|
|
self::base256_lshift($leading_ones, $current_bits);
|
|
|
|
$temp = str_pad($temp, strlen($leading_ones), chr(0), STR_PAD_LEFT);
|
|
|
|
return $this->normalize(new static($leading_ones | $temp, 256));
|
|
}
|
|
|
|
/**
|
|
* Logical Left Shift
|
|
*
|
|
* Shifts binary strings $shift bits, essentially multiplying by 2**$shift.
|
|
*
|
|
* @param $x String
|
|
* @param $shift Integer
|
|
* @return string
|
|
*/
|
|
protected static function base256_lshift(&$x, $shift)
|
|
{
|
|
if ($shift == 0) {
|
|
return;
|
|
}
|
|
|
|
$num_bytes = $shift >> 3; // eg. floor($shift/8)
|
|
$shift &= 7; // eg. $shift % 8
|
|
|
|
$carry = 0;
|
|
for ($i = strlen($x) - 1; $i >= 0; --$i) {
|
|
$temp = ord($x[$i]) << $shift | $carry;
|
|
$x[$i] = chr($temp);
|
|
$carry = $temp >> 8;
|
|
}
|
|
$carry = ($carry != 0) ? chr($carry) : '';
|
|
$x = $carry . $x . str_repeat(chr(0), $num_bytes);
|
|
}
|
|
|
|
/**
|
|
* Logical Left Rotate
|
|
*
|
|
* Instead of the top x bits being dropped they're appended to the shifted bit string.
|
|
*
|
|
* @param int $shift
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public function bitwise_leftRotate($shift)
|
|
{
|
|
$bits = $this->toBytes();
|
|
|
|
if ($this->precision > 0) {
|
|
$precision = $this->precision;
|
|
if (static::FAST_BITWISE) {
|
|
$mask = $this->bitmask->toBytes();
|
|
} else {
|
|
$mask = $this->bitmask->subtract(new static(1));
|
|
$mask = $mask->toBytes();
|
|
}
|
|
} else {
|
|
$temp = ord($bits[0]);
|
|
for ($i = 0; $temp >> $i; ++$i) {
|
|
}
|
|
$precision = 8 * strlen($bits) - 8 + $i;
|
|
$mask = chr((1 << ($precision & 0x7)) - 1) . str_repeat(chr(0xFF), $precision >> 3);
|
|
}
|
|
|
|
if ($shift < 0) {
|
|
$shift+= $precision;
|
|
}
|
|
$shift%= $precision;
|
|
|
|
if (!$shift) {
|
|
return clone $this;
|
|
}
|
|
|
|
$left = $this->bitwise_leftShift($shift);
|
|
$left = $left->bitwise_and(new static($mask, 256));
|
|
$right = $this->bitwise_rightShift($precision - $shift);
|
|
$result = static::FAST_BITWISE ? $left->bitwise_or($right) : $left->add($right);
|
|
return $this->normalize($result);
|
|
}
|
|
|
|
/**
|
|
* Logical Right Rotate
|
|
*
|
|
* Instead of the bottom x bits being dropped they're prepended to the shifted bit string.
|
|
*
|
|
* @param int $shift
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public function bitwise_rightRotate($shift)
|
|
{
|
|
return $this->bitwise_leftRotate(-$shift);
|
|
}
|
|
|
|
/**
|
|
* Returns the smallest and largest n-bit number
|
|
*
|
|
* @param int $bits
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public static function minMaxBits($bits)
|
|
{
|
|
$bytes = $bits >> 3;
|
|
$min = str_repeat(chr(0), $bytes);
|
|
$max = str_repeat(chr(0xFF), $bytes);
|
|
$msb = $bits & 7;
|
|
if ($msb) {
|
|
$min = chr(1 << ($msb - 1)) . $min;
|
|
$max = chr((1 << $msb) - 1) . $max;
|
|
} else {
|
|
$min[0] = chr(0x80);
|
|
}
|
|
return [
|
|
'min' => new static($min, 256),
|
|
'max' => new static($max, 256)
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Return the size of a BigInteger in bits
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getLength()
|
|
{
|
|
return strlen($this->toBits());
|
|
}
|
|
|
|
/**
|
|
* Return the size of a BigInteger in bytes
|
|
*
|
|
* @return int
|
|
*/
|
|
public function getLengthInBytes()
|
|
{
|
|
return strlen($this->toBytes());
|
|
}
|
|
|
|
/**
|
|
* Performs some pre-processing for powMod
|
|
*
|
|
* @return bool|Engine
|
|
*/
|
|
protected function powModOuter(Engine $e, Engine $n)
|
|
{
|
|
$n = $this->bitmask !== false && $this->bitmask->compare($n) < 0 ? $this->bitmask : $n->abs();
|
|
|
|
if ($e->compare(new static()) < 0) {
|
|
$e = $e->abs();
|
|
|
|
$temp = $this->modInverse($n);
|
|
if ($temp === false) {
|
|
return false;
|
|
}
|
|
|
|
return $this->normalize($temp->powModInner($e, $n));
|
|
}
|
|
|
|
return $this->powModInner($e, $n);
|
|
}
|
|
|
|
/**
|
|
* Sliding Window k-ary Modular Exponentiation
|
|
*
|
|
* Based on {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap14.pdf#page=27 HAC 14.85} /
|
|
* {@link http://math.libtomcrypt.com/files/tommath.pdf#page=210 MPM 7.7}. In a departure from those algorithims,
|
|
* however, this function performs a modular reduction after every multiplication and squaring operation.
|
|
* As such, this function has the same preconditions that the reductions being used do.
|
|
*
|
|
* @param \phpseclib\Math\BigInteger\Engines\Engine $x
|
|
* @param \phpseclib\Math\BigInteger\Engines\Engine $e
|
|
* @param \phpseclib\Math\BigInteger\Engines\Engine $n
|
|
* @param string $class
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
protected static function slidingWindow(Engine $x, Engine $e, Engine $n, $class)
|
|
{
|
|
static $window_ranges = [7, 25, 81, 241, 673, 1793]; // from BigInteger.java's oddModPow function
|
|
//static $window_ranges = [0, 7, 36, 140, 450, 1303, 3529]; // from MPM 7.3.1
|
|
|
|
$e_bits = $e->toBits();
|
|
$e_length = strlen($e_bits);
|
|
|
|
// calculate the appropriate window size.
|
|
// $window_size == 3 if $window_ranges is between 25 and 81, for example.
|
|
for ($i = 0, $window_size = 1; $i < count($window_ranges) && $e_length > $window_ranges[$i]; ++$window_size, ++$i) {
|
|
}
|
|
|
|
$n_value = $n->value;
|
|
|
|
if (method_exists(static::class, 'generateCustomReduction')) {
|
|
static::generateCustomReduction($n, $class);
|
|
}
|
|
|
|
// precompute $this^0 through $this^$window_size
|
|
$powers = [];
|
|
$powers[1] = static::prepareReduce($x->value, $n_value, $class);
|
|
$powers[2] = static::squareReduce($powers[1], $n_value, $class);
|
|
|
|
// we do every other number since substr($e_bits, $i, $j+1) (see below) is supposed to end
|
|
// in a 1. ie. it's supposed to be odd.
|
|
$temp = 1 << ($window_size - 1);
|
|
for ($i = 1; $i < $temp; ++$i) {
|
|
$i2 = $i << 1;
|
|
$powers[$i2 + 1] = static::multiplyReduce($powers[$i2 - 1], $powers[2], $n_value, $class);
|
|
}
|
|
|
|
$result = new $class(1);
|
|
$result = static::prepareReduce($result->value, $n_value, $class);
|
|
|
|
for ($i = 0; $i < $e_length;) {
|
|
if (!$e_bits[$i]) {
|
|
$result = static::squareReduce($result, $n_value, $class);
|
|
++$i;
|
|
} else {
|
|
for ($j = $window_size - 1; $j > 0; --$j) {
|
|
if (!empty($e_bits[$i + $j])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
// eg. the length of substr($e_bits, $i, $j + 1)
|
|
for ($k = 0; $k <= $j; ++$k) {
|
|
$result = static::squareReduce($result, $n_value, $class);
|
|
}
|
|
|
|
$result = static::multiplyReduce($result, $powers[bindec(substr($e_bits, $i, $j + 1))], $n_value, $class);
|
|
|
|
$i += $j + 1;
|
|
}
|
|
}
|
|
|
|
$temp = new $class();
|
|
$temp->value = static::reduce($result, $n_value, $class);
|
|
|
|
return $temp;
|
|
}
|
|
|
|
/**
|
|
* Generates a random number of a certain size
|
|
*
|
|
* Bit length is equal to $size
|
|
*
|
|
* @param int $size
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public static function random($size)
|
|
{
|
|
extract(static::minMaxBits($size));
|
|
return static::randomRange($min, $max);
|
|
}
|
|
|
|
/**
|
|
* Generates a random prime number of a certain size
|
|
*
|
|
* Bit length is equal to $size
|
|
*
|
|
* @param int $size
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public static function randomPrime($size)
|
|
{
|
|
extract(static::minMaxBits($size));
|
|
return static::randomRangePrime($min, $max);
|
|
}
|
|
|
|
/**
|
|
* Performs some pre-processing for randomRangePrime
|
|
*
|
|
* @return bool|Engine
|
|
*/
|
|
protected static function randomRangePrimeOuter(Engine $min, Engine $max)
|
|
{
|
|
$compare = $max->compare($min);
|
|
|
|
if (!$compare) {
|
|
return $min->isPrime() ? $min : false;
|
|
} elseif ($compare < 0) {
|
|
// if $min is bigger then $max, swap $min and $max
|
|
$temp = $max;
|
|
$max = $min;
|
|
$min = $temp;
|
|
}
|
|
|
|
$x = static::randomRange($min, $max);
|
|
|
|
return static::randomRangePrimeInner($x, $min, $max);
|
|
}
|
|
|
|
/**
|
|
* Generate a random number between a range
|
|
*
|
|
* Returns a random number between $min and $max where $min and $max
|
|
* can be defined using one of the two methods:
|
|
*
|
|
* BigInteger::randomRange($min, $max)
|
|
* BigInteger::randomRange($max, $min)
|
|
*
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
protected static function randomRangeHelper(Engine $min, Engine $max)
|
|
{
|
|
$compare = $max->compare($min);
|
|
|
|
if (!$compare) {
|
|
return $min;
|
|
} elseif ($compare < 0) {
|
|
// if $min is bigger then $max, swap $min and $max
|
|
$temp = $max;
|
|
$max = $min;
|
|
$min = $temp;
|
|
}
|
|
|
|
if (!isset(static::$one)) {
|
|
static::$one = new static(1);
|
|
}
|
|
|
|
$max = $max->subtract($min->subtract(static::$one));
|
|
|
|
$size = strlen(ltrim($max->toBytes(), chr(0)));
|
|
|
|
/*
|
|
doing $random % $max doesn't work because some numbers will be more likely to occur than others.
|
|
eg. if $max is 140 and $random's max is 255 then that'd mean both $random = 5 and $random = 145
|
|
would produce 5 whereas the only value of random that could produce 139 would be 139. ie.
|
|
not all numbers would be equally likely. some would be more likely than others.
|
|
|
|
creating a whole new random number until you find one that is within the range doesn't work
|
|
because, for sufficiently small ranges, the likelihood that you'd get a number within that range
|
|
would be pretty small. eg. with $random's max being 255 and if your $max being 1 the probability
|
|
would be pretty high that $random would be greater than $max.
|
|
|
|
phpseclib works around this using the technique described here:
|
|
|
|
http://crypto.stackexchange.com/questions/5708/creating-a-small-number-from-a-cryptographically-secure-random-string
|
|
*/
|
|
$random_max = new static(chr(1) . str_repeat("\0", $size), 256);
|
|
$random = new static(Random::string($size), 256);
|
|
|
|
list($max_multiple) = $random_max->divide($max);
|
|
$max_multiple = $max_multiple->multiply($max);
|
|
|
|
while ($random->compare($max_multiple) >= 0) {
|
|
$random = $random->subtract($max_multiple);
|
|
$random_max = $random_max->subtract($max_multiple);
|
|
$random = $random->bitwise_leftShift(8);
|
|
$random = $random->add(new static(Random::string(1), 256));
|
|
$random_max = $random_max->bitwise_leftShift(8);
|
|
list($max_multiple) = $random_max->divide($max);
|
|
$max_multiple = $max_multiple->multiply($max);
|
|
}
|
|
list(, $random) = $random->divide($max);
|
|
|
|
return $random->add($min);
|
|
}
|
|
|
|
/**
|
|
* Performs some post-processing for randomRangePrime
|
|
*
|
|
* @return bool|Engine
|
|
*/
|
|
protected static function randomRangePrimeInner(Engine $x, Engine $min, Engine $max)
|
|
{
|
|
if (!isset(static::$two)) {
|
|
static::$two = new static('2');
|
|
}
|
|
|
|
$x->make_odd();
|
|
if ($x->compare($max) > 0) {
|
|
// if $x > $max then $max is even and if $min == $max then no prime number exists between the specified range
|
|
if ($min->equals($max)) {
|
|
return false;
|
|
}
|
|
$x = clone $min;
|
|
$x->make_odd();
|
|
}
|
|
|
|
$initial_x = clone $x;
|
|
|
|
while (true) {
|
|
if ($x->isPrime()) {
|
|
return $x;
|
|
}
|
|
|
|
$x = $x->add(static::$two);
|
|
|
|
if ($x->compare($max) > 0) {
|
|
$x = clone $min;
|
|
if ($x->equals(static::$two)) {
|
|
return $x;
|
|
}
|
|
$x->make_odd();
|
|
}
|
|
|
|
if ($x->equals($initial_x)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sets the $t parameter for primality testing
|
|
*
|
|
* @return int
|
|
*/
|
|
protected function setupIsPrime()
|
|
{
|
|
$length = $this->getLengthInBytes();
|
|
|
|
// see HAC 4.49 "Note (controlling the error probability)"
|
|
// @codingStandardsIgnoreStart
|
|
if ($length >= 163) { $t = 2; } // floor(1300 / 8)
|
|
else if ($length >= 106) { $t = 3; } // floor( 850 / 8)
|
|
else if ($length >= 81 ) { $t = 4; } // floor( 650 / 8)
|
|
else if ($length >= 68 ) { $t = 5; } // floor( 550 / 8)
|
|
else if ($length >= 56 ) { $t = 6; } // floor( 450 / 8)
|
|
else if ($length >= 50 ) { $t = 7; } // floor( 400 / 8)
|
|
else if ($length >= 43 ) { $t = 8; } // floor( 350 / 8)
|
|
else if ($length >= 37 ) { $t = 9; } // floor( 300 / 8)
|
|
else if ($length >= 31 ) { $t = 12; } // floor( 250 / 8)
|
|
else if ($length >= 25 ) { $t = 15; } // floor( 200 / 8)
|
|
else if ($length >= 18 ) { $t = 18; } // floor( 150 / 8)
|
|
else { $t = 27; }
|
|
// @codingStandardsIgnoreEnd
|
|
|
|
return $t;
|
|
}
|
|
|
|
/**
|
|
* Tests Primality
|
|
*
|
|
* Uses the {@link http://en.wikipedia.org/wiki/Miller%E2%80%93Rabin_primality_test Miller-Rabin primality test}.
|
|
* See {@link http://www.cacr.math.uwaterloo.ca/hac/about/chap4.pdf#page=8 HAC 4.24} for more info.
|
|
*
|
|
* @param int $t
|
|
* @return bool
|
|
*/
|
|
protected function testPrimality($t)
|
|
{
|
|
if (!$this->testSmallPrimes()) {
|
|
return false;
|
|
}
|
|
|
|
$n = clone $this;
|
|
$n_1 = $n->subtract(static::$one);
|
|
$n_2 = $n->subtract(static::$two);
|
|
|
|
$r = clone $n_1;
|
|
$s = static::scan1divide($r);
|
|
|
|
for ($i = 0; $i < $t; ++$i) {
|
|
$a = static::randomRange(static::$two, $n_2);
|
|
$y = $a->modPow($r, $n);
|
|
|
|
if (!$y->equals(static::$one) && !$y->equals($n_1)) {
|
|
for ($j = 1; $j < $s && !$y->equals($n_1); ++$j) {
|
|
$y = $y->modPow(static::$two, $n);
|
|
if ($y->equals(static::$one)) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!$y->equals($n_1)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Checks a numer to see if it's prime
|
|
*
|
|
* Assuming the $t parameter is not set, this function has an error rate of 2**-80. The main motivation for the
|
|
* $t parameter is distributability. BigInteger::randomPrime() can be distributed across multiple pageloads
|
|
* on a website instead of just one.
|
|
*
|
|
* @param int $t
|
|
* @return bool
|
|
*/
|
|
public function isPrime($t = false)
|
|
{
|
|
if (!$t) {
|
|
$t = $this->setupIsPrime();
|
|
}
|
|
return $this->testPrimality($t);
|
|
}
|
|
|
|
/**
|
|
* Performs a few preliminary checks on root
|
|
*
|
|
* @param int $n
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
protected function rootHelper($n)
|
|
{
|
|
if ($n < 1) {
|
|
return clone static::$zero;
|
|
} // we want positive exponents
|
|
if ($this->compare(static::$one) < 0) {
|
|
return clone static::$zero;
|
|
} // we want positive numbers
|
|
if ($this->compare(static::$two) < 0) {
|
|
return clone static::$one;
|
|
} // n-th root of 1 or 2 is 1
|
|
|
|
return $this->rootInner($n);
|
|
}
|
|
|
|
/**
|
|
* Calculates the nth root of a biginteger.
|
|
*
|
|
* Returns the nth root of a positive biginteger, where n defaults to 2
|
|
*
|
|
* @param int $n
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
* @internal This function is based off of {@link http://mathforum.org/library/drmath/view/52605.html this page} and {@link http://stackoverflow.com/questions/11242920/calculating-nth-root-with-bcmath-in-php this stackoverflow question}.
|
|
*/
|
|
protected function rootInner($n)
|
|
{
|
|
$n = new static($n);
|
|
|
|
// g is our guess number
|
|
$g = static::$two;
|
|
// while (g^n < num) g=g*2
|
|
while ($g->pow($n)->compare($this) < 0) {
|
|
$g = $g->multiply(static::$two);
|
|
}
|
|
// if (g^n==num) num is a power of 2, we're lucky, end of job
|
|
// == 0 bccomp(bcpow($g, $n), $n->value)==0
|
|
if ($g->pow($n)->equals($this) > 0) {
|
|
$root = $g;
|
|
return $this->normalize($root);
|
|
}
|
|
|
|
// if we're here num wasn't a power of 2 :(
|
|
$og = $g; // og means original guess and here is our upper bound
|
|
$g = $g->divide(static::$two)[0]; // g is set to be our lower bound
|
|
$step = $og->subtract($g)->divide(static::$two)[0]; // step is the half of upper bound - lower bound
|
|
$g = $g->add($step); // we start at lower bound + step , basically in the middle of our interval
|
|
|
|
// while step>1
|
|
|
|
while ($step->compare(static::$one) == 1) {
|
|
$guess = $g->pow($n);
|
|
$step = $step->divide(static::$two)[0];
|
|
$comp = $guess->compare($this); // compare our guess with real number
|
|
switch ($comp) {
|
|
case -1: // if guess is lower we add the new step
|
|
$g = $g->add($step);
|
|
break;
|
|
case 1: // if guess is higher we sub the new step
|
|
$g = $g->subtract($step);
|
|
break;
|
|
case 0: // if guess is exactly the num we're done, we return the value
|
|
$root = $g;
|
|
break 2;
|
|
}
|
|
}
|
|
|
|
if ($comp == 1) {
|
|
$g = $g->subtract($step);
|
|
}
|
|
|
|
// whatever happened, g is the closest guess we can make so return it
|
|
$root = $g;
|
|
|
|
return $this->normalize($root);
|
|
}
|
|
|
|
/**
|
|
* Calculates the nth root of a biginteger.
|
|
*
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
public function root($n = 2)
|
|
{
|
|
return $this->rootHelper($n);
|
|
}
|
|
|
|
/**
|
|
* Return the minimum BigInteger between an arbitrary number of BigIntegers.
|
|
*
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
protected static function minHelper(array $nums)
|
|
{
|
|
if (count($nums) == 1) {
|
|
return $nums[0];
|
|
}
|
|
$min = $nums[0];
|
|
for ($i = 1; $i < count($nums); $i++) {
|
|
$min = $min->compare($nums[$i]) > 0 ? $nums[$i] : $min;
|
|
}
|
|
return $min;
|
|
}
|
|
|
|
/**
|
|
* Return the minimum BigInteger between an arbitrary number of BigIntegers.
|
|
*
|
|
* @return \phpseclib\Math\BigInteger\Engines\Engine
|
|
*/
|
|
protected static function maxHelper(array $nums)
|
|
{
|
|
if (count($nums) == 1) {
|
|
return $nums[0];
|
|
}
|
|
$max = $nums[0];
|
|
for ($i = 1; $i < count($nums); $i++) {
|
|
$max = $max->compare($nums[$i]) < 0 ? $nums[$i] : $max;
|
|
}
|
|
return $max;
|
|
}
|
|
} |