Subversion Repositories php_utils

Rev

Rev 5 | Blame | Compare with Previous | Last modification | View Log | RSS feed

  1. <?php
  2.  
  3. /*
  4.  * PHP GMP-Supplement implemented using BCMath
  5.  * Copyright 2020-2022 Daniel Marschall, ViaThinkSoft
  6.  * Version 2021-06-29
  7.  *
  8.  * Licensed under the Apache License, Version 2.0 (the "License");
  9.  * you may not use this file except in compliance with the License.
  10.  * You may obtain a copy of the License at
  11.  *
  12.  *     http://www.apache.org/licenses/LICENSE-2.0
  13.  *
  14.  * Unless required by applicable law or agreed to in writing, software
  15.  * distributed under the License is distributed on an "AS IS" BASIS,
  16.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  17.  * See the License for the specific language governing permissions and
  18.  * limitations under the License.
  19.  */
  20.  
  21. if (function_exists('bcadd')) {
  22.         // ----------------- Implementation of GMP functions using BCMath -----------------
  23.  
  24.         if (!function_exists('gmp_init') ) {
  25.                 define('GMP_ROUND_ZERO',     0);
  26.                 define('GMP_ROUND_PLUSINF',  1);
  27.                 define('GMP_ROUND_MINUSINF', 2);
  28.                 define('GMP_MSW_FIRST',      1);
  29.                 define('GMP_LSW_FIRST',      2);
  30.                 define('GMP_LITTLE_ENDIAN',  4);
  31.                 define('GMP_BIG_ENDIAN',     8);
  32.                 define('GMP_NATIVE_ENDIAN', 16);
  33.                 define('GMP_VERSION',  '6.0.0');
  34.  
  35.                 // gmp_abs ( GMP $a ) : GMP
  36.                 // Absolute value
  37.                 function gmp_abs($a) {
  38.                         bcscale(0);
  39.                         if (bccomp($a, "0") == 1) {
  40.                                 return $a;
  41.                         } else {
  42.                                 return bcmul($a, "-1");
  43.                         }
  44.                 }
  45.  
  46.                 // gmp_add ( GMP $a , GMP $b ) : GMP
  47.                 // Add numbers
  48.                 function gmp_add($a, $b) {
  49.                         bcscale(0);
  50.  
  51.                         // bcadd ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : string
  52.                         return bcadd($a, $b);
  53.                 }
  54.  
  55.                 // gmp_and ( GMP $a , GMP $b ) : GMP
  56.                 // Bitwise AND
  57.                 function gmp_and($a, $b) {
  58.                         bcscale(0);
  59.                         // Convert $a and $b to a binary string
  60.                         $ab = bc_dec2bin($a);
  61.                         $bb = bc_dec2bin($b);
  62.                         $length = max(strlen($ab), strlen($bb));
  63.                         $ab = str_pad($ab, $length, "0", STR_PAD_LEFT);
  64.                         $bb = str_pad($bb, $length, "0", STR_PAD_LEFT);
  65.  
  66.                         // Do the bitwise binary operation
  67.                         $cb = '';
  68.                         for ($i=0; $i<$length; $i++) {
  69.                                 $cb .= (($ab[$i] == 1) and ($bb[$i] == 1)) ? '1' : '0';
  70.                         }
  71.  
  72.                         // Convert back to a decimal number
  73.                         return bc_bin2dec($cb);
  74.                 }
  75.  
  76.                 // gmp_binomial ( mixed $n , int $k ) : GMP
  77.                 // Calculates binomial coefficient
  78.                 function gmp_binomial($n, $k) {
  79.                         bcscale(0);
  80.                         throw new Exception("gmp_binomial() NOT IMPLEMENTED");
  81.                 }
  82.  
  83.                 // gmp_clrbit ( GMP $a , int $index ) : void
  84.                 // Clear bit
  85.                 function gmp_clrbit(&$a, $index) {
  86.                         bcscale(0);
  87.                         gmp_setbit($a, $index, false);
  88.                 }
  89.  
  90.                 // gmp_cmp ( GMP $a , GMP $b ) : int
  91.                 // Compare numbers
  92.                 function gmp_cmp($a, $b) {
  93.                         bcscale(0);
  94.  
  95.                         // bccomp ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : int
  96.                         return bccomp($a, $b);
  97.                 }
  98.  
  99.                 // gmp_com ( GMP $a ) : GMP
  100.                 // Calculates one's complement
  101.                 function gmp_com($a) {
  102.                         bcscale(0);
  103.                         // Convert $a and $b to a binary string
  104.                         $ab = bc_dec2bin($a);
  105.  
  106.                         // Swap every bit
  107.                         for ($i=0; $i<strlen($ab); $i++) {
  108.                                 $ab[$i] = ($ab[$i] == '1' ? '0' : '1');
  109.                         }
  110.  
  111.                         // Convert back to a decimal number
  112.                         return bc_bin2dec($ab);
  113.                 }
  114.  
  115.                 // gmp_div_q ( GMP $a , GMP $b [, int $round = GMP_ROUND_ZERO ] ) : GMP
  116.                 // Divide numbers
  117.                 function gmp_div_q($a, $b, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
  118.                         bcscale(0);
  119.  
  120.                         // bcdiv ( string $dividend , string $divisor [, int $scale = 0 ] ) : string
  121.                         return bcdiv($a, $b);
  122.                 }
  123.  
  124.                 // Divide numbers and get quotient and remainder
  125.                 // gmp_div_qr ( GMP $n , GMP $d [, int $round = GMP_ROUND_ZERO ] ) : array
  126.                 function gmp_div_qr($n, $d, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
  127.                         bcscale(0);
  128.                         return array(
  129.                                 gmp_div_q($n, $d, $round),
  130.                                 gmp_div_r($n, $d, $round)
  131.                         );
  132.                 }
  133.  
  134.                 // Remainder of the division of numbers
  135.                 // gmp_div_r ( GMP $n , GMP $d [, int $round = GMP_ROUND_ZERO ] ) : GMP
  136.                 function gmp_div_r($n, $d, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
  137.                         bcscale(0);
  138.                         // The remainder operator can be used with negative integers. The rule is:
  139.                         // - Perform the operation as if both operands were positive.
  140.                         // - If the left operand is negative, then make the result negative.
  141.                         // - If the left operand is positive, then make the result positive.
  142.                         // - Ignore the sign of the right operand in all cases.
  143.                         $r = bcmod($n, $d);
  144.                         if (bccomp($n, "0") < 0) $r = bcmul($r, "-1");
  145.                         return $r;
  146.                 }
  147.  
  148.                 // gmp_div ( GMP $a , GMP $b [, int $round = GMP_ROUND_ZERO ] ) : GMP
  149.                 // Divide numbers
  150.                 function gmp_div($a, $b, $round = GMP_ROUND_ZERO/*$round not implemented*/) {
  151.                         bcscale(0);
  152.                         return gmp_div_q($a, $b, $round); // Alias von gmp_div_q
  153.                 }
  154.  
  155.                 // gmp_divexact ( GMP $n , GMP $d ) : GMP
  156.                 // Exact division of numbers
  157.                 function gmp_divexact($n, $d) {
  158.                         bcscale(0);
  159.                         return bcdiv($n, $d);
  160.                 }
  161.  
  162.                 // gmp_export ( GMP $gmpnumber [, int $word_size = 1 [, int $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN ]] ) : string
  163.                 // Export to a binary string
  164.                 function gmp_export($gmpnumber, $word_size = 1, $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) {
  165.                         if ($word_size != 1) throw new Exception("Word size != 1 not implemented");
  166.                         if ($options != GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) throw new Exception("Different options not implemented");
  167.  
  168.                         bcscale(0);
  169.                         $gmpnumber = bcmul($gmpnumber,"1"); // normalize
  170.                         return gmp_init(bin2hex($gmpnumber), 16);
  171.                 }
  172.  
  173.                 // gmp_fact ( mixed $a ) : GMP
  174.                 // Factorial
  175.                 function gmp_fact($a) {
  176.                         bcscale(0);
  177.                         return bcfact($a);
  178.                 }
  179.  
  180.                 // gmp_gcd ( GMP $a , GMP $b ) : GMP
  181.                 // Calculate GCD
  182.                 function gmp_gcd($a, $b) {
  183.                         bcscale(0);
  184.                         return gmp_gcdext($a, $b)['g'];
  185.                 }
  186.  
  187.                 // gmp_gcdext ( GMP $a , GMP $b ) : array
  188.                 // Calculate GCD and multipliers
  189.                 function gmp_gcdext($a, $b) {
  190.                         bcscale(0);
  191.  
  192.                         // Source: https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Math/BigInteger/Engines/BCMath.php#L285
  193.                         // modified to make it fit here and to be compatible with gmp_gcdext
  194.  
  195.                         $s = '1';
  196.                         $t = '0';
  197.                         $s_ = '0';
  198.                         $t_ = '1';
  199.  
  200.                         while (bccomp($b, '0', 0) != 0) {
  201.                                 $q = bcdiv($a, $b, 0);
  202.  
  203.                                 $temp = $a;
  204.                                 $a = $b;
  205.                                 $b = bcsub($temp, bcmul($b, $q, 0), 0);
  206.  
  207.                                 $temp = $s;
  208.                                 $s = $s_;
  209.                                 $s_ = bcsub($temp, bcmul($s, $q, 0), 0);
  210.  
  211.                                 $temp = $t;
  212.                                 $t = $t_;
  213.                                 $t_ = bcsub($temp, bcmul($t, $q, 0), 0);
  214.                         }
  215.  
  216.                         return [
  217.                                 'g' => /*$this->normalize*/($a),
  218.                                 's' => /*$this->normalize*/($s),
  219.                                 't' => /*$this->normalize*/($t)
  220.                         ];
  221.                 }
  222.  
  223.                 // gmp_hamdist ( GMP $a , GMP $b ) : int
  224.                 // Hamming distance
  225.                 function gmp_hamdist($a, $b) {
  226.                         bcscale(0);
  227.                         throw new Exception("gmp_hamdist() NOT IMPLEMENTED");
  228.                 }
  229.  
  230.                 // gmp_import ( string $data [, int $word_size = 1 [, int $options = GMP_MSW_FIRST | GMP_NATIVE_ENDIAN ]] ) : GMP
  231.                 // Import from a binary string
  232.                 function gmp_import($data, $word_size=1, $options=GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) {
  233.                         bcscale(0);
  234.  
  235.                         if ($word_size != 1) throw new Exception("Word size != 1 not implemented");
  236.                         if ($options != GMP_MSW_FIRST | GMP_NATIVE_ENDIAN) throw new Exception("Different options not implemented");
  237.  
  238.                         return gmp_init(hex2bin(gmp_strval(gmp_init($data), 16)));
  239.                 }
  240.  
  241.                 // gmp_init ( mixed $number [, int $base = 0 ] ) : GMP
  242.                 // Create GMP number
  243.                 function gmp_init($number, $base=0) {
  244.                         bcscale(0);
  245.                         if ($base == 0) {
  246.                                 // If base is 0 (default value), the actual base is determined from the leading characters:
  247.                                 // if the first two characters are 0x or 0X, hexadecimal is assumed,
  248.                                 // otherwise if the first character is "0", octal is assumed,
  249.                                 // otherwise decimal is assumed.
  250.                                 if (strtoupper(substr($number, 0, 2)) == '0X') {
  251.                                         $base = 16;
  252.                                 } else if (strtoupper(substr($number, 0, 1)) == '0') {
  253.                                         $base = 8;
  254.                                 } else {
  255.                                         $base = 10;
  256.                                 }
  257.                         }
  258.  
  259.                         if ($base == 10) {
  260.                                 return $number;
  261.                         } else {
  262.                                 return base_convert_bigint($number, $base, 10);
  263.                         }
  264.                 }
  265.  
  266.                 // gmp_intval ( GMP $gmpnumber ) : int
  267.                 // Convert GMP number to integer
  268.                 function gmp_intval($gmpnumber) {
  269.                         bcscale(0);
  270.                         return (int)$gmpnumber;
  271.                 }
  272.  
  273.                 // gmp_invert ( GMP $a , GMP $b ) : GMP
  274.                 // Inverse by modulo
  275.                 function gmp_invert($a, $b) {
  276.                         bcscale(0);
  277.  
  278.                         // Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L246
  279.  
  280.                         while (bccomp($a, '0')==-1) {
  281.                                 $a=bcadd($b, $a);
  282.                         }
  283.                         while (bccomp($b, $a)==-1) {
  284.                                 $a=bcmod($a, $b);
  285.                         }
  286.                         $c=$a;
  287.                         $d=$b;
  288.                         $uc=1;
  289.                         $vc=0;
  290.                         $ud=0;
  291.                         $vd=1;
  292.                         while (bccomp($c, '0')!=0) {
  293.                                 $temp1=$c;
  294.                                 $q=bcdiv($d, $c, 0);
  295.                                 $c=bcmod($d, $c);
  296.                                 $d=$temp1;
  297.                                 $temp2=$uc;
  298.                                 $temp3=$vc;
  299.                                 $uc=bcsub($ud, bcmul($q, $uc));
  300.                                 $vc=bcsub($vd, bcmul($q, $vc));
  301.                                 $ud=$temp2;
  302.                                 $vd=$temp3;
  303.                         }
  304.                         $result='';
  305.                         if (bccomp($d, '1')==0) {
  306.                                 if (bccomp($ud, '0')==1) {
  307.                                         $result=$ud;
  308.                                 } else {
  309.                                         $result=bcadd($ud, $b);
  310.                                 }
  311.                         } else {
  312.                                 throw new ErrorException("ERROR: $a and $b are NOT relatively prime.");
  313.                         }
  314.                         return $result;
  315.                 }
  316.  
  317.                 // gmp_jacobi ( GMP $a , GMP $p ) : int
  318.                 // Jacobi symbol
  319.                 function gmp_jacobi($a, $p) {
  320.                         bcscale(0);
  321.  
  322.                         // Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L136
  323.  
  324.                         if ($p>=3 && $p%2==1) {
  325.                                 $a = bcmod($a, $p);
  326.                                 if ($a == '0') return '0';
  327.                                 if ($a == '1') return '1';
  328.                                 $a1 = $a;
  329.                                 $e = 0;
  330.                                 while (bcmod($a1, '2') == '0') {
  331.                                         $a1 = bcdiv($a1, '2');
  332.                                         $e = bcadd($e, '1');
  333.                                 }
  334.                                 $s = (bcmod($e, '2')=='0' || bcmod($p, '8')=='1' || bcmod($p, '8')=='7') ? '1' : '-1';
  335.                                 if ($a1 == '1') return $s;
  336.                                 if (bcmod($p, '4')=='3' && bcmod($a1, '4')=='3') $s = -$s;
  337.                                 return bcmul($s, (string)gmp_jacobi(bcmod($p, $a1), $a1));
  338.                         } else {
  339.                                 return false;
  340.                         }
  341.                 }
  342.  
  343.                 // gmp_kronecker ( mixed $a , mixed $b ) : int
  344.                 // Kronecker symbol
  345.                 function gmp_kronecker($a, $b) {
  346.                         bcscale(0);
  347.                         throw new Exception("gmp_kronecker() NOT IMPLEMENTED");
  348.                 }
  349.  
  350.                 // gmp_lcm ( mixed $a , mixed $b ) : GMP
  351.                 // Calculate LCM
  352.                 function gmp_lcm($a, $b) {
  353.                         bcscale(0);
  354.  
  355.                         if ((bccomp($a,'0')==0) && (bccomp($b,'0')==0)) {
  356.                                 return '0';
  357.                         } else {
  358.                                 return gmp_div(gmp_abs(gmp_mul($a,$b)), gmp_gcd($a,$b));
  359.                         }
  360.                 }
  361.  
  362.                 // gmp_legendre ( GMP $a , GMP $p ) : int
  363.                 // Legendre symbol
  364.                 function gmp_legendre($a, $p) {
  365.                         bcscale(0);
  366.                         throw new Exception("gmp_legendre() NOT IMPLEMENTED");
  367.                 }
  368.  
  369.                 // gmp_mod ( GMP $n , GMP $d ) : GMP
  370.                 // Modulo operation
  371.                 function gmp_mod($n, $d) {
  372.                         bcscale(0);
  373.  
  374.                         // bcmod ( string $dividend , string $divisor [, int $scale = 0 ] ) : string
  375.                         return bcmod($n, $d);
  376.                 }
  377.  
  378.                 // gmp_mul ( GMP $a , GMP $b ) : GMP
  379.                 // Multiply numbers
  380.                 function gmp_mul($a, $b) {
  381.                         bcscale(0);
  382.  
  383.                         // bcmul ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : string
  384.                         return bcmul($a, $b);
  385.                 }
  386.  
  387.                 // gmp_neg ( GMP $a ) : GMP
  388.                 // Negate number
  389.                 function gmp_neg($a) {
  390.                         bcscale(0);
  391.                         return bcmul($a, "-1");
  392.                 }
  393.  
  394.                 // gmp_nextprime ( int $a ) : GMP
  395.                 // Find next prime number
  396.                 function gmp_nextprime($a) {
  397.                         bcscale(0);
  398.  
  399.                         // Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L692
  400.  
  401.                         if (bccomp($a, '2') == '-1') {
  402.                                 return '2';
  403.                         }
  404.                         $result = gmp_or(bcadd($a, '1'), '1');
  405.                         while (!gmp_prob_prime($result)) {
  406.                                 $result = bcadd($result, '2');
  407.                         }
  408.                         return $result;
  409.                 }
  410.  
  411.                 // gmp_or ( GMP $a , GMP $b ) : GMP
  412.                 // Bitwise OR
  413.                 function gmp_or($a, $b) {
  414.                         bcscale(0);
  415.                         // Convert $a and $b to a binary string
  416.                         $ab = bc_dec2bin($a);
  417.                         $bb = bc_dec2bin($b);
  418.                         $length = max(strlen($ab), strlen($bb));
  419.                         $ab = str_pad($ab, $length, "0", STR_PAD_LEFT);
  420.                         $bb = str_pad($bb, $length, "0", STR_PAD_LEFT);
  421.  
  422.                         // Do the bitwise binary operation
  423.                         $cb = '';
  424.                         for ($i=0; $i<$length; $i++) {
  425.                                 $cb .= (($ab[$i] == 1) or ($bb[$i] == 1)) ? '1' : '0';
  426.                         }
  427.  
  428.                         // Convert back to a decimal number
  429.                         return bc_bin2dec($cb);
  430.                 }
  431.  
  432.                 // gmp_perfect_power ( mixed $a ) : bool
  433.                 // Perfect power check
  434.                 function gmp_perfect_power($a) {
  435.                         bcscale(0);
  436.                         throw new Exception("gmp_perfect_power() NOT IMPLEMENTED");
  437.                 }
  438.  
  439.                 // gmp_perfect_square ( GMP $a ) : bool
  440.                 // Perfect square check
  441.                 function gmp_perfect_square($a) {
  442.                         bcscale(0);
  443.                         throw new Exception("gmp_perfect_square() NOT IMPLEMENTED");
  444.                 }
  445.  
  446.                 // gmp_popcount ( GMP $a ) : int
  447.                 // Population count
  448.                 function gmp_popcount($a) {
  449.                         bcscale(0);
  450.                         $ab = bc_dec2bin($a);
  451.                         return substr_count($ab, '1');
  452.                 }
  453.  
  454.                 // gmp_pow ( GMP $base , int $exp ) : GMP
  455.                 // Raise number into power
  456.                 function gmp_pow($base, $exp) {
  457.                         bcscale(0);
  458.  
  459.                         // bcpow ( string $base , string $exponent [, int $scale = 0 ] ) : string
  460.                         return bcpow($base, $exp);
  461.                 }
  462.  
  463.                 // gmp_powm ( GMP $base , GMP $exp , GMP $mod ) : GMP
  464.                 // Raise number into power with modulo
  465.                 function gmp_powm($base, $exp, $mod) {
  466.                         bcscale(0);
  467.  
  468.                         // bcpowmod ( string $base , string $exponent , string $modulus [, int $scale = 0 ] ) : string
  469.                         return bcpowmod($base, $exp, $mod);
  470.                 }
  471.  
  472.                 // gmp_prob_prime ( GMP $a [, int $reps = 10 ] ) : int
  473.                 // Check if number is "probably prime"
  474.                 function gmp_prob_prime($a, $reps=10) {
  475.                         bcscale(0);
  476.  
  477.                         // Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/NumberTheory.php#L655
  478.  
  479.                         $t = 40;
  480.                         $k = 0;
  481.                         $m = bcsub($reps, '1');
  482.                         while (bcmod($m, '2') == '0') {
  483.                                 $k = bcadd($k, '1');
  484.                                 $m = bcdiv($m, '2');
  485.                         }
  486.                         for ($i=0; $i<$t; $i++) {
  487.                                 $a = bcrand('1', bcsub($reps, '1'));
  488.                                 if ($m < 0) {
  489.                                         return new ErrorException("Negative exponents ($m) not allowed");
  490.                                 } else {
  491.                                         $b0 = bcpowmod($a, $m, $reps);
  492.                                 }
  493.                                 if ($b0!=1 && $b0!=bcsub($reps, '1')) {
  494.                                         $j = 1;
  495.                                         while ($j<=$k-1 && $b0!=bcsub($reps, '1')) {
  496.                                                 $b0 = bcpowmod($b0, '2', $reps);
  497.                                                 if ($b0 == 1) {
  498.                                                         return false;
  499.                                                 }
  500.                                                 $j++;
  501.                                         }
  502.                                         if ($b0 != bcsub($reps, '1')) {
  503.                                                 return false;
  504.                                         }
  505.                                 }
  506.                         }
  507.                         return true;
  508.                 }
  509.  
  510.                 // gmp_random_bits ( int $bits ) : GMP
  511.                 // Random number
  512.                 function gmp_random_bits($bits) {
  513.                         bcscale(0);
  514.                         $min = 0;
  515.                         $max = bcsub(bcpow('2', $bits), '1');
  516.                         return bcrand($min, $max);
  517.                 }
  518.  
  519.                 // gmp_random_range ( GMP $min , GMP $max ) : GMP
  520.                 // Random number
  521.                 function gmp_random_range($min, $max) {
  522.                         bcscale(0);
  523.                         return bcrand($min, $max);
  524.                 }
  525.  
  526.                 // gmp_random_seed ( mixed $seed ) : void
  527.                 // Sets the RNG seed
  528.                 function gmp_random_seed($seed) {
  529.                         bcscale(0);
  530.                         bcrand_seed($seed);
  531.                 }
  532.  
  533.                 // gmp_random ([ int $limiter = 20 ] ) : GMP
  534.                 // Random number (deprecated)
  535.                 function gmp_random($limiter=20) {
  536.                         bcscale(0);
  537.                         throw new Exception("gmp_random() is deprecated! Please use gmp_random_bits() or gmp_random_range() instead.");
  538.                 }
  539.  
  540.                 // gmp_root ( GMP $a , int $nth ) : GMP
  541.                 // Take the integer part of nth root
  542.                 function gmp_root($a, $nth) {
  543.                         bcscale(0);
  544.                         throw new Exception("gmp_root() NOT IMPLEMENTED");
  545.                 }
  546.  
  547.                 // gmp_rootrem ( GMP $a , int $nth ) : array
  548.                 // Take the integer part and remainder of nth root
  549.                 function gmp_rootrem($a, $nth) {
  550.                         bcscale(0);
  551.                         throw new Exception("gmp_rootrem() NOT IMPLEMENTED");
  552.                 }
  553.  
  554.                 // gmp_scan0 ( GMP $a , int $start ) : int
  555.                 // Scan for 0
  556.                 function gmp_scan0($a, $start) {
  557.                         bcscale(0);
  558.  
  559.                         $ab = bc_dec2bin($a);
  560.  
  561.                         if ($start < 0) throw new Exception("Starting index must be greater than or equal to zero");
  562.                         if ($start >= strlen($ab)) return $start;
  563.  
  564.                         for ($i=$start; $i<strlen($ab); $i++) {
  565.                                 if ($ab[strlen($ab)-1-$i] == '0') {
  566.                                         return $i;
  567.                                 }
  568.                         }
  569.  
  570.                         return false;
  571.                 }
  572.  
  573.                 // gmp_scan1 ( GMP $a , int $start ) : int
  574.                 // Scan for 1
  575.                 function gmp_scan1($a, $start) {
  576.                         bcscale(0);
  577.  
  578.                         $ab = bc_dec2bin($a);
  579.  
  580.                         if ($start < 0) throw new Exception("Starting index must be greater than or equal to zero");
  581.                         if ($start >= strlen($ab)) return -1;
  582.  
  583.                         for ($i=$start; $i<strlen($ab); $i++) {
  584.                                 if ($ab[strlen($ab)-1-$i] == '1') {
  585.                                         return $i;
  586.                                 }
  587.                         }
  588.  
  589.                         return false;
  590.                 }
  591.  
  592.                 // gmp_setbit ( GMP $a , int $index [, bool $bit_on = TRUE ] ) : void
  593.                 // Set bit
  594.                 function gmp_setbit(&$a, $index, $bit_on=TRUE) {
  595.                         bcscale(0);
  596.                         $ab = bc_dec2bin($a);
  597.  
  598.                         if ($index < 0) throw new Exception("Invalid index");
  599.                         if ($index >= strlen($ab)) {
  600.                                 $ab = str_pad($ab, $index+1, '0', STR_PAD_LEFT);
  601.                         }
  602.  
  603.                         $ab[strlen($ab)-1-$index] = $bit_on ? '1' : '0';
  604.  
  605.                         $a = bc_bin2dec($ab);
  606.                 }
  607.  
  608.                 // gmp_sign ( GMP $a ) : int
  609.                 // Sign of number
  610.                 function gmp_sign($a) {
  611.                         bcscale(0);
  612.                         return bccomp($a, "0");
  613.                 }
  614.  
  615.                 // gmp_sqrt ( GMP $a ) : GMP
  616.                 // Calculate square root
  617.                 function gmp_sqrt($a) {
  618.                         bcscale(0);
  619.  
  620.                         // bcsqrt ( string $operand [, int $scale = 0 ] ) : string
  621.                         return bcsqrt($a);
  622.                 }
  623.  
  624.                 // gmp_sqrtrem ( GMP $a ) : array
  625.                 // Square root with remainder
  626.                 function gmp_sqrtrem($a) {
  627.                         bcscale(0);
  628.                         throw new Exception("gmp_sqrtrem() NOT IMPLEMENTED");
  629.                 }
  630.  
  631.                 // gmp_strval ( GMP $gmpnumber [, int $base = 10 ] ) : string
  632.                 // Convert GMP number to string
  633.                 function gmp_strval($gmpnumber, $base=10) {
  634.                         bcscale(0);
  635.                         if ($base == 10) {
  636.                                 return $gmpnumber;
  637.                         } else {
  638.                                 return base_convert_bigint($gmpnumber, 10, $base);
  639.                         }
  640.                 }
  641.  
  642.                 // gmp_sub ( GMP $a , GMP $b ) : GMP
  643.                 // Subtract numbers
  644.                 function gmp_sub($a, $b) {
  645.                         bcscale(0);
  646.  
  647.                         // bcsub ( string $left_operand , string $right_operand [, int $scale = 0 ] ) : string
  648.                         return bcsub($a, $b);
  649.                 }
  650.  
  651.                 // gmp_testbit ( GMP $a , int $index ) : bool
  652.                 // Tests if a bit is set
  653.                 function gmp_testbit($a, $index) {
  654.                         bcscale(0);
  655.                         $ab = bc_dec2bin($a);
  656.  
  657.                         if ($index < 0) throw new Exception("Invalid index");
  658.                         if ($index >= strlen($ab)) return ('0' == '1');
  659.  
  660.                         return $ab[strlen($ab)-1-$index] == '1';
  661.                 }
  662.  
  663.                 // gmp_xor ( GMP $a , GMP $b ) : GMP
  664.                 // Bitwise XOR
  665.                 function gmp_xor($a, $b) {
  666.                         bcscale(0);
  667.                         // Convert $a and $b to a binary string
  668.                         $ab = bc_dec2bin($a);
  669.                         $bb = bc_dec2bin($b);
  670.                         $length = max(strlen($ab), strlen($bb));
  671.                         $ab = str_pad($ab, $length, "0", STR_PAD_LEFT);
  672.                         $bb = str_pad($bb, $length, "0", STR_PAD_LEFT);
  673.  
  674.                         // Do the bitwise binary operation
  675.                         $cb = '';
  676.                         for ($i=0; $i<$length; $i++) {
  677.                                 $cb .= (($ab[$i] == 1) xor ($bb[$i] == 1)) ? '1' : '0';
  678.                         }
  679.  
  680.                         // Convert back to a decimal number
  681.                         return bc_bin2dec($cb);
  682.                 }
  683.         }
  684.  
  685.         // ----------------- Helper functions -----------------
  686.  
  687.         function base_convert_bigint($numstring, $frombase, $tobase) {
  688.                 $numstring = "".$numstring;
  689.  
  690.                 $frombase_str = '';
  691.                 for ($i=0; $i<$frombase; $i++) {
  692.                         $frombase_str .= strtoupper(base_convert((string)$i, 10, 36));
  693.                 }
  694.  
  695.                 $tobase_str = '';
  696.                 for ($i=0; $i<$tobase; $i++) {
  697.                         $tobase_str .= strtoupper(base_convert((string)$i, 10, 36));
  698.                 }
  699.  
  700.                 $length = strlen($numstring);
  701.                 $result = '';
  702.                 $number = array();
  703.                 for ($i = 0; $i < $length; $i++) {
  704.                         $number[$i] = stripos($frombase_str, $numstring[$i]);
  705.                 }
  706.                 do { // Loop until whole number is converted
  707.                         $divide = 0;
  708.                         $newlen = 0;
  709.                         for ($i = 0; $i < $length; $i++) { // Perform division manually (which is why this works with big numbers)
  710.                                 $divide = $divide * $frombase + $number[$i];
  711.                                 if ($divide >= $tobase) {
  712.                                         $number[$newlen++] = (int)($divide / $tobase);
  713.                                         $divide = $divide % $tobase;
  714.                                 } else if ($newlen > 0) {
  715.                                         $number[$newlen++] = 0;
  716.                                 }
  717.                         }
  718.                         $length = $newlen;
  719.                         $result = $tobase_str[$divide] . $result; // Divide is basically $numstring % $tobase (i.e. the new character)
  720.                 }
  721.                 while ($newlen != 0);
  722.  
  723.                 return $result;
  724.         }
  725.  
  726.         function bc_dec2bin($decimal_i) {
  727.                 // https://www.exploringbinary.com/base-conversion-in-php-using-bcmath/
  728.  
  729.                 bcscale(0);
  730.  
  731.                 $binary_i = '';
  732.                 do {
  733.                         $binary_i = bcmod($decimal_i,'2') . $binary_i;
  734.                         $decimal_i = bcdiv($decimal_i,'2');
  735.                 } while (bccomp($decimal_i,'0'));
  736.  
  737.                 return $binary_i;
  738.         }
  739.  
  740.         function bc_bin2dec($binary_i) {
  741.                 // https://www.exploringbinary.com/base-conversion-in-php-using-bcmath/
  742.  
  743.                 bcscale(0);
  744.  
  745.                 $decimal_i = '0';
  746.                 for ($i = 0; $i < strlen($binary_i); $i++) {
  747.                         $decimal_i = bcmul($decimal_i,'2');
  748.                         $decimal_i = bcadd($decimal_i,$binary_i[$i]);
  749.                 }
  750.  
  751.                 return $decimal_i;
  752.         }
  753.  
  754.         // ----------------- New functions -----------------
  755.  
  756.         // Newly added: gmp_not / bcnot
  757.         function bcnot($a) {
  758.                 bcscale(0);
  759.                 // Convert $a to a binary string
  760.                 $ab = bc_dec2bin($a);
  761.                 $length = strlen($ab);
  762.  
  763.                 // Do the bitwise binary operation
  764.                 $cb = '';
  765.                 for ($i=0; $i<$length; $i++) {
  766.                         $cb .= ($ab[$i] == 1) ? '0' : '1';
  767.                 }
  768.  
  769.                 // Convert back to a decimal number
  770.                 return bc_bin2dec($cb);
  771.         }
  772.         function gmp_not($a) {
  773.                 bcscale(0);
  774.                 return bcnot($a);
  775.         }
  776.  
  777.         // Newly added: bcshiftl / gmp_shiftl
  778.         function bcshiftl($num, $bits) {
  779.                 bcscale(0);
  780.                 return bcmul($num, bcpow('2', $bits));
  781.         }
  782.         function gmp_shiftl($num, $bits) {
  783.                 bcscale(0);
  784.                 return bcshiftl($num, $bits);
  785.         }
  786.  
  787.         // Newly added: bcshiftr / gmp_shiftr
  788.         function bcshiftr($num, $bits) {
  789.                 bcscale(0);
  790.                 return bcdiv($num, bcpow('2', $bits));
  791.         }
  792.         function gmp_shiftr($num, $bits) {
  793.                 bcscale(0);
  794.                 return bcshiftr($num, $bits);
  795.         }
  796.  
  797.         // Newly added: bcfact (used by gmp_fact)
  798.         function bcfact($a) {
  799.                 bcscale(0);
  800.  
  801.                 // Source: https://www.php.net/manual/de/book.bc.php#116510
  802.  
  803.                 if (!filter_var($a, FILTER_VALIDATE_INT) || $a <= 0) {
  804.                         throw new InvalidArgumentException(sprintf('Argument must be natural number, "%s" given.', $a));
  805.                 }
  806.  
  807.                 for ($result = '1'; $a > 0; $a--) {
  808.                         $result = bcmul($result, $a);
  809.                 }
  810.  
  811.                 return $result;
  812.         }
  813.  
  814.         // Newly added (used by gmp_prob_prime, gmp_random_range and gmp_random_bits)
  815.         function bcrand($min, $max = false) {
  816.                 bcscale(0);
  817.                 // Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/BCMathUtils.php#L7
  818.                 // Fixed: https://github.com/CityOfZion/neo-php/issues/16
  819.                 if (!$max) {
  820.                         $max = $min;
  821.                         $min = 0;
  822.                 }
  823.                 return bcadd(bcmul(bcdiv((string)mt_rand(), (string)mt_getrandmax(), strlen($max)), bcsub(bcadd($max, '1'), $min)), $min);
  824.         }
  825.  
  826.         // Newly added (used by gmp_random_seed)
  827.         function bcrand_seed($seed) {
  828.                 bcscale(0);
  829.                 mt_srand($seed);
  830.         }
  831. }
  832.