Subversion Repositories php_utils

Rev

Rev 2 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 daniel-mar 1
<?php
2
 
3
/*
4
 * PHP GMP-Supplement implemented using BCMath
5
 * Copyright 2020-2021 Daniel Marschall, ViaThinkSoft
6
 * Version 2021-05-21
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
                $frombase_str = '';
689
                for ($i=0; $i<$frombase; $i++) {
690
                        $frombase_str .= strtoupper(base_convert((string)$i, 10, 36));
691
                }
692
 
693
                $tobase_str = '';
694
                for ($i=0; $i<$tobase; $i++) {
695
                        $tobase_str .= strtoupper(base_convert((string)$i, 10, 36));
696
                }
697
 
698
                $length = strlen($numstring);
699
                $result = '';
700
                $number = array();
701
                for ($i = 0; $i < $length; $i++) {
702
                        $number[$i] = stripos($frombase_str, $numstring[$i]);
703
                }
704
                do { // Loop until whole number is converted
705
                        $divide = 0;
706
                        $newlen = 0;
707
                        for ($i = 0; $i < $length; $i++) { // Perform division manually (which is why this works with big numbers)
708
                                $divide = $divide * $frombase + $number[$i];
709
                                if ($divide >= $tobase) {
710
                                        $number[$newlen++] = (int)($divide / $tobase);
711
                                        $divide = $divide % $tobase;
712
                                } else if ($newlen > 0) {
713
                                        $number[$newlen++] = 0;
714
                                }
715
                        }
716
                        $length = $newlen;
717
                        $result = $tobase_str[$divide] . $result; // Divide is basically $numstring % $tobase (i.e. the new character)
718
                }
719
                while ($newlen != 0);
720
 
721
                return $result;
722
        }
723
 
724
        function bc_dec2bin($decimal_i) {
725
                // https://www.exploringbinary.com/base-conversion-in-php-using-bcmath/
726
 
727
                bcscale(0);
728
 
729
                $binary_i = '';
730
                do {
731
                        $binary_i = bcmod($decimal_i,'2') . $binary_i;
732
                        $decimal_i = bcdiv($decimal_i,'2');
733
                } while (bccomp($decimal_i,'0'));
734
 
735
                return $binary_i;
736
        }
737
 
738
        function bc_bin2dec($binary_i) {
739
                // https://www.exploringbinary.com/base-conversion-in-php-using-bcmath/
740
 
741
                bcscale(0);
742
 
743
                $decimal_i = '0';
744
                for ($i = 0; $i < strlen($binary_i); $i++) {
745
                        $decimal_i = bcmul($decimal_i,'2');
746
                        $decimal_i = bcadd($decimal_i,$binary_i[$i]);
747
                }
748
 
749
                return $decimal_i;
750
        }
751
 
752
        // ----------------- New functions -----------------
753
 
754
        // Newly added: gmp_not / bcnot
755
        function bcnot($a) {
756
                bcscale(0);
757
                // Convert $a to a binary string
758
                $ab = bc_dec2bin($a);
759
                $length = strlen($ab);
760
 
761
                // Do the bitwise binary operation
762
                $cb = '';
763
                for ($i=0; $i<$length; $i++) {
764
                        $cb .= ($ab[$i] == 1) ? '0' : '1';
765
                }
766
 
767
                // Convert back to a decimal number
768
                return bc_bin2dec($cb);
769
        }
770
        function gmp_not($a) {
771
                bcscale(0);
772
                return bcnot($a);
773
        }
774
 
775
        // Newly added: bcshiftl / gmp_shiftl
776
        function bcshiftl($num, $bits) {
777
                bcscale(0);
778
                return bcmul($num, bcpow('2', $bits));
779
        }
780
        function gmp_shiftl($num, $bits) {
781
                bcscale(0);
782
                return bcshiftl($num, $bits);
783
        }
784
 
785
        // Newly added: bcshiftr / gmp_shiftr
786
        function bcshiftr($num, $bits) {
787
                bcscale(0);
788
                return bcdiv($num, bcpow('2', $bits));
789
        }
790
        function gmp_shiftr($num, $bits) {
791
                bcscale(0);
792
                return bcshiftr($num, $bits);
793
        }
794
 
795
        // Newly added: bcfact (used by gmp_fact)
796
        function bcfact($a) {
797
                bcscale(0);
798
 
799
                // Source: https://www.php.net/manual/de/book.bc.php#116510
800
 
801
                if (!filter_var($a, FILTER_VALIDATE_INT) || $a <= 0) {
802
                        throw new InvalidArgumentException(sprintf('Argument must be natural number, "%s" given.', $a));
803
                }
804
 
805
                for ($result = '1'; $a > 0; $a--) {
806
                        $result = bcmul($result, $a);
807
                }
808
 
809
                return $result;
810
        }
811
 
812
        // Newly added (used by gmp_prob_prime, gmp_random_range and gmp_random_bits)
813
        function bcrand($min, $max = false) {
814
                bcscale(0);
815
                // Source: https://github.com/CityOfZion/neo-php/blob/master/src/Crypto/BCMathUtils.php#L7
816
                // Fixed: https://github.com/CityOfZion/neo-php/issues/16
817
                if (!$max) {
818
                        $max = $min;
819
                        $min = 0;
820
                }
821
                return bcadd(bcmul(bcdiv((string)mt_rand(), (string)mt_getrandmax(), strlen($max)), bcsub(bcadd($max, '1'), $min)), $min);
822
        }
823
 
824
        // Newly added (used by gmp_random_seed)
825
        function bcrand_seed($seed) {
826
                bcscale(0);
827
                mt_srand($seed);
828
        }
829
}