1: <?php
2: /*
3: * SimpleID
4: *
5: * Copyright (C) Kelvin Mo 2009-2025
6: *
7: * This program is free software; you can redistribute it and/or
8: * modify it under the terms of the GNU General Public
9: * License as published by the Free Software Foundation; either
10: * version 2 of the License, or (at your option) any later version.
11: *
12: * This program is distributed in the hope that it will be useful,
13: * but WITHOUT ANY WARRANTY; without even the implied warranty of
14: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15: * General Public License for more details.
16: *
17: * You should have received a copy of the GNU General Public
18: * License along with this program; if not, write to the Free
19: * Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20: *
21: */
22:
23: /**
24: * Abstraction library for multiple precision mathematics. This file uses either
25: * the GNU Multiple Precision Arithmic Libary (GMP) if it is installed, or the
26: * default BCMath library if it is not installed.
27: *
28: * @package simpleid
29: * @filesource
30: */
31:
32: namespace SimpleID\Crypt;
33:
34: if (function_exists('gmp_init')) {
35: /**
36: * Defines whether the GMP library is available.
37: */
38: define('BIGNUM_GMP', true);
39: } else {
40: /** @ignore */
41: define('BIGNUM_GMP', false);
42: }
43:
44: /**
45: * A generic big integer using the GMP or the BCMath library.
46: */
47: class BigNum {
48: /** @var \GMP|string the internal representation of the value */
49: protected $value;
50:
51: /**
52: * Returns whether either the GMP or the BCMath library is installed. If neither
53: * of these libraries are installed, the functions in this file will not work.
54: *
55: * @return bool true if either GMP or BCMath is installed.
56: */
57: static function loaded() {
58: return (function_exists('gmp_init') || function_exists('bcadd'));
59: }
60:
61: /**
62: * Creates a bignum.
63: *
64: * @param mixed $str An integer, a string in base 2 to 36, or a byte stream in base 256
65: * @param int $base an integer between 2 and 36, or 256
66: */
67: public function __construct($str, $base = 10) {
68: switch ($base) {
69: case 10:
70: if (BIGNUM_GMP) {
71: $this->value = gmp_init($str, 10);
72: } else {
73: $this->value = $str;
74: }
75: return;
76: case 256:
77: $unpacked = unpack('C*', $str);
78: assert($unpacked != false);
79: $bytes = array_merge($unpacked);
80:
81: $value = (new BigNum(0))->value;
82:
83: foreach ($bytes as $byte) {
84: $value = $this->_mul($value, 256);
85: $value = $this->_add($value, (new BigNum($byte))->value);
86: }
87: $this->value = $value;
88: return;
89: default:
90: if (!is_integer($base) || ($base < 2) || ($base > 36))
91: throw new \InvalidArgumentException('Invalid base');
92:
93: $value = (new BigNum(0))->value;
94:
95: for ($i = 0; $i < strlen($str); $i++) {
96: $value = $this->_mul($value, $base);
97: $value = $this->_add($value, (new BigNum(base_convert($str[$i], $base, 10)))->value);
98: }
99: $this->value = $value;
100: return;
101: }
102: }
103:
104: /**
105: * Converts a bignum into a string representation (base 2 to 36) or a byte stream
106: * (base 256)
107: *
108: * @param int $base an integer between 2 and 36, or 256
109: * @return string|false the converted bignum
110: */
111: function val($base = 10) {
112: switch ($base) {
113: case 10:
114: if (BIGNUM_GMP) {
115: $base10 = gmp_strval($this->value, 10);
116: } else {
117: /** @var string $base10 */
118: $base10 = $this->value;
119: }
120:
121: return $base10;
122:
123: case 256:
124: $cmp = $this->_cmp($this->value, 0);
125: if ($cmp < 0) {
126: return FALSE;
127: }
128:
129: if ($cmp == 0) {
130: return "\x00";
131: }
132:
133: $bytes = [];
134: $num = $this->value;
135:
136: while ($this->_cmp($num, 0) > 0) {
137: $x = $this->_mod($num, 256);
138: if ($x instanceof \GMP) $x = gmp_intval($x);
139: array_unshift($bytes, (int) $x);
140: $num = $this->_div($num, 256);
141: }
142:
143: if ($bytes && ($bytes[0] > 127)) {
144: array_unshift($bytes, 0);
145: }
146:
147: $byte_stream = '';
148: foreach ($bytes as $byte) {
149: $byte_stream .= pack('C', $byte);
150: }
151:
152: return $byte_stream;
153:
154: default:
155: if (!is_integer($base) || ($base < 2) || ($base > 36)) return FALSE;
156:
157: $cmp = $this->_cmp($this->value, 0);
158: if ($cmp < 0) {
159: return FALSE;
160: }
161:
162: if ($cmp == 0) {
163: return "0";
164: }
165:
166: $str = '';
167: $num = $this->value;
168:
169: while ($this->_cmp($num, 0) > 0) {
170: $r = $this->_mod($num, $base);
171: if (BIGNUM_GMP) {
172: $i = gmp_intval($r);
173: } else {
174: /** @var string $r */
175: $i = intval($r);
176: }
177: $str = base_convert(strval($i), 10, $base) . $str;
178: $num = $this->_div($num, $base);
179: }
180:
181: return $str;
182: }
183: }
184:
185: /**
186: * Adds two bignums
187: *
188: * @param BigNum $b
189: * @return BigNum a bignum representing this + b
190: */
191: function add($b) {
192: $result = new BigNum(0);
193: $result->value = $this->_add($this->value, $b->value);
194: return $result;
195: }
196:
197: /**
198: * Multiplies two bignums
199: *
200: * @param BigNum $b
201: * @return BigNum a bignum representing this * b
202: */
203: function mul($b) {
204: $result = new BigNum(0);
205: $result->value = $this->_mul($this->value, $b->value);
206: return $result;
207: }
208:
209: /**
210: * Raise base to power exp
211: *
212: * @param int $exp the exponent
213: * @return BigNum a bignum representing this ^ exp
214: */
215: function pow($exp) {
216: $result = new BigNum(0);
217: $result->value = $this->_pow($this->value, $exp);
218: return $result;
219: }
220:
221: /**
222: * Divides two bignums
223: *
224: * @param BigNum $b
225: * @return BigNum a bignum representing this / b
226: */
227: function div($b) {
228: $result = new BigNum(0);
229: $result->value = $this->_div($this->value, $b->value);
230: return $result;
231: }
232:
233: /**
234: * Returns n modulo d
235: *
236: * @param BigNum $d
237: * @return BigNum a bignum representing this mod d
238: */
239: function mod($d) {
240: $result = new BigNum(0);
241: $result->value = $this->_mod($this->value, $d->value);
242: return $result;
243: }
244:
245: /**
246: * Raise a number into power with modulo
247: *
248: * @param BigNum $exp the exponent
249: * @param BigNum $mod the modulo
250: * @return BigNum a bignum representing this ^ exp mod mod
251: */
252: function powmod($exp, $mod) {
253: $result = new BigNum(0);
254: $result->value = $this->_powmod($this->value, $exp->value, $mod->value);
255: return $result;
256: }
257:
258: /**
259: * Compares two bignum
260: *
261: * @param BigNum $b
262: * @return int positive value if this > b, zero if this = b and a negative value if this < b
263: */
264: function cmp($b) {
265: return $this->_cmp($this->value, $b->value);
266: }
267:
268: /**
269: * Returns a string representation.
270: *
271: * @return string
272: */
273: function __toString() {
274: $val = $this->val();
275: return ($val) ? $val : 'NaN';
276: }
277:
278: /**
279: * Adds two bignums
280: *
281: * @param \GMP|string $a
282: * @param \GMP|string $b
283: * @return \GMP|string a bignum representing a + b
284: */
285: protected function _add($a, $b) {
286: if (BIGNUM_GMP) {
287: return gmp_add($a, $b);
288: } else {
289: /** @var numeric-string $a */
290: /** @var numeric-string $b */
291: return bcadd($a, $b);
292: }
293: }
294:
295: /**
296: * Multiplies two bignums
297: *
298: * @param \GMP|int|string $a
299: * @param \GMP|int|string $b
300: * @return \GMP|string a bignum representing a * b
301: */
302: protected function _mul($a, $b) {
303: if (BIGNUM_GMP) {
304: return gmp_mul($a, $b);
305: } else {
306: /** @var numeric-string $a */
307: /** @var numeric-string $b */
308: return bcmul($a, $b);
309: }
310: }
311:
312: /**
313: * Divides two bignums
314: *
315: * @param \GMP|int|string $a
316: * @param \GMP|int|string $b
317: * @return \GMP|string a bignum representing a / b
318: */
319: protected function _div($a, $b) {
320: if (BIGNUM_GMP) {
321: return gmp_div($a, $b);
322: } else {
323: /** @var string $a */
324: /** @var string $b */
325: return bcdiv($a, $b);
326: }
327: }
328:
329: /**
330: * Raise base to power exp
331: *
332: * @param \GMP|string $base the base
333: * @param int|string $exp the exponent, as an integer or a bignum
334: * @return \GMP|string a bignum representing base ^ exp
335: */
336: function _pow($base, $exp) {
337: if (BIGNUM_GMP) {
338: return gmp_pow($base, intval($exp));
339: } else {
340: /** @var numeric-string $base */
341: /** @var numeric-string $exp */
342: $exp = strval($exp);
343: return bcpow($base, $exp);
344: }
345: }
346:
347: /**
348: * Returns n modulo d
349: *
350: * @param \GMP|int|string $n
351: * @param \GMP|int|string $d
352: * @return \GMP|string a bignum representing n mod d
353: */
354: protected function _mod($n, $d) {
355: if (BIGNUM_GMP) {
356: return gmp_mod($n, $d);
357: } else {
358: /** @var string $n */
359: /** @var string $d */
360: return bcmod($n, $d);
361: }
362: }
363:
364: /**
365: * Raise a number into power with modulo
366: *
367: * @param \GMP|string $base the base
368: * @param \GMP|string $exp the exponent
369: * @param \GMP|string $mod the modulo
370: * @return \GMP|string|null a bignum representing base ^ exp mod mod
371: */
372: protected function _powmod($base, $exp, $mod) {
373: if (BIGNUM_GMP) {
374: return gmp_powm($base, $exp, $mod);
375: } elseif (function_exists('bcpowmod')) {
376: /** @var string $base */
377: /** @var string $exp */
378: /** @var string $mod */
379: $result = bcpowmod($base, $exp, $mod);
380: if ($result == false) return null;
381: return $result;
382: } else {
383: $square = $this->_mod($base, $mod);
384: $result = '1';
385: while ($this->_cmp($exp, 0) > 0) {
386: if ($this->_mod($exp, 2)) {
387: $result = $this->_mod($this->_mul($result, $square), $mod);
388: }
389: $square = $this->_mod($this->_mul($square, $square), $mod);
390: $exp = $this->_div($exp, 2);
391: }
392: return $result;
393: }
394: }
395:
396: /**
397: * Compares two bignum
398: *
399: * @param \GMP|int|string $a
400: * @param \GMP|int|string $b
401: * @return int positive value if a > b, zero if a = b and a negative value if a < b
402: */
403: protected function _cmp($a, $b) {
404: if (BIGNUM_GMP) {
405: return gmp_cmp($a, $b);
406: } else {
407: /** @var numeric-string $a */
408: /** @var numeric-string $b */
409: return bccomp($a, $b);
410: }
411: }
412: }
413:
414: ?>
415: