BigMath.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. <?php
  2. /**
  3. * BigMath: A math library wrapper that abstracts out the underlying
  4. * long integer library.
  5. *
  6. * PHP versions 4 and 5
  7. *
  8. * LICENSE: See the COPYING file included in this distribution.
  9. *
  10. * @access private
  11. * @package OpenID
  12. * @author JanRain, Inc. <openid@janrain.com>
  13. * @copyright 2005-2008 Janrain, Inc.
  14. * @license http://www.apache.org/licenses/LICENSE-2.0 Apache
  15. */
  16. /**
  17. * Needed for random number generation
  18. */
  19. require_once 'Auth/OpenID/CryptUtil.php';
  20. /**
  21. * Need Auth_OpenID::bytes().
  22. */
  23. require_once 'Auth/OpenID.php';
  24. /**
  25. * The superclass of all big-integer math implementations
  26. * @access private
  27. * @package OpenID
  28. */
  29. class Auth_OpenID_MathLibrary {
  30. /**
  31. * Given a long integer, returns the number converted to a binary
  32. * string. This function accepts long integer values of arbitrary
  33. * magnitude and uses the local large-number math library when
  34. * available.
  35. *
  36. * @param integer $long The long number (can be a normal PHP
  37. * integer or a number created by one of the available long number
  38. * libraries)
  39. * @return string $binary The binary version of $long
  40. */
  41. function longToBinary($long)
  42. {
  43. $cmp = $this->cmp($long, 0);
  44. if ($cmp < 0) {
  45. $msg = __FUNCTION__ . " takes only positive integers.";
  46. trigger_error($msg, E_USER_ERROR);
  47. return null;
  48. }
  49. if ($cmp == 0) {
  50. return "\x00";
  51. }
  52. $bytes = array();
  53. while ($this->cmp($long, 0) > 0) {
  54. array_unshift($bytes, $this->mod($long, 256));
  55. $long = $this->div($long, pow(2, 8));
  56. }
  57. if ($bytes && ($bytes[0] > 127)) {
  58. array_unshift($bytes, 0);
  59. }
  60. $string = '';
  61. foreach ($bytes as $byte) {
  62. $string .= pack('C', $byte);
  63. }
  64. return $string;
  65. }
  66. /**
  67. * Given a binary string, returns the binary string converted to a
  68. * long number.
  69. *
  70. * @param string $binary The binary version of a long number,
  71. * probably as a result of calling longToBinary
  72. * @return integer $long The long number equivalent of the binary
  73. * string $str
  74. */
  75. function binaryToLong($str)
  76. {
  77. if ($str === null) {
  78. return null;
  79. }
  80. // Use array_merge to return a zero-indexed array instead of a
  81. // one-indexed array.
  82. $bytes = array_merge(unpack('C*', $str));
  83. $n = $this->init(0);
  84. if ($bytes && ($bytes[0] > 127)) {
  85. trigger_error("bytesToNum works only for positive integers.",
  86. E_USER_WARNING);
  87. return null;
  88. }
  89. foreach ($bytes as $byte) {
  90. $n = $this->mul($n, pow(2, 8));
  91. $n = $this->add($n, $byte);
  92. }
  93. return $n;
  94. }
  95. function base64ToLong($str)
  96. {
  97. $b64 = base64_decode($str);
  98. if ($b64 === false) {
  99. return false;
  100. }
  101. return $this->binaryToLong($b64);
  102. }
  103. function longToBase64($str)
  104. {
  105. return base64_encode($this->longToBinary($str));
  106. }
  107. /**
  108. * Returns a random number in the specified range. This function
  109. * accepts $start, $stop, and $step values of arbitrary magnitude
  110. * and will utilize the local large-number math library when
  111. * available.
  112. *
  113. * @param integer $start The start of the range, or the minimum
  114. * random number to return
  115. * @param integer $stop The end of the range, or the maximum
  116. * random number to return
  117. * @param integer $step The step size, such that $result - ($step
  118. * * N) = $start for some N
  119. * @return integer $result The resulting randomly-generated number
  120. */
  121. function rand($stop)
  122. {
  123. static $duplicate_cache = array();
  124. // Used as the key for the duplicate cache
  125. $rbytes = $this->longToBinary($stop);
  126. if (array_key_exists($rbytes, $duplicate_cache)) {
  127. list($duplicate, $nbytes) = $duplicate_cache[$rbytes];
  128. } else {
  129. if ($rbytes[0] == "\x00") {
  130. $nbytes = Auth_OpenID::bytes($rbytes) - 1;
  131. } else {
  132. $nbytes = Auth_OpenID::bytes($rbytes);
  133. }
  134. $mxrand = $this->pow(256, $nbytes);
  135. // If we get a number less than this, then it is in the
  136. // duplicated range.
  137. $duplicate = $this->mod($mxrand, $stop);
  138. if (count($duplicate_cache) > 10) {
  139. $duplicate_cache = array();
  140. }
  141. $duplicate_cache[$rbytes] = array($duplicate, $nbytes);
  142. }
  143. do {
  144. $bytes = "\x00" . Auth_OpenID_CryptUtil::getBytes($nbytes);
  145. $n = $this->binaryToLong($bytes);
  146. // Keep looping if this value is in the low duplicated range
  147. } while ($this->cmp($n, $duplicate) < 0);
  148. return $this->mod($n, $stop);
  149. }
  150. }
  151. /**
  152. * Exposes BCmath math library functionality.
  153. *
  154. * {@link Auth_OpenID_BcMathWrapper} wraps the functionality provided
  155. * by the BCMath extension.
  156. *
  157. * @access private
  158. * @package OpenID
  159. */
  160. class Auth_OpenID_BcMathWrapper extends Auth_OpenID_MathLibrary{
  161. var $type = 'bcmath';
  162. function add($x, $y)
  163. {
  164. return bcadd($x, $y);
  165. }
  166. function sub($x, $y)
  167. {
  168. return bcsub($x, $y);
  169. }
  170. function pow($base, $exponent)
  171. {
  172. return bcpow($base, $exponent);
  173. }
  174. function cmp($x, $y)
  175. {
  176. return bccomp($x, $y);
  177. }
  178. function init($number, $base = 10)
  179. {
  180. return $number;
  181. }
  182. function mod($base, $modulus)
  183. {
  184. return bcmod($base, $modulus);
  185. }
  186. function mul($x, $y)
  187. {
  188. return bcmul($x, $y);
  189. }
  190. function div($x, $y)
  191. {
  192. return bcdiv($x, $y);
  193. }
  194. /**
  195. * Same as bcpowmod when bcpowmod is missing
  196. *
  197. * @access private
  198. */
  199. function _powmod($base, $exponent, $modulus)
  200. {
  201. $square = $this->mod($base, $modulus);
  202. $result = 1;
  203. while($this->cmp($exponent, 0) > 0) {
  204. if ($this->mod($exponent, 2)) {
  205. $result = $this->mod($this->mul($result, $square), $modulus);
  206. }
  207. $square = $this->mod($this->mul($square, $square), $modulus);
  208. $exponent = $this->div($exponent, 2);
  209. }
  210. return $result;
  211. }
  212. function powmod($base, $exponent, $modulus)
  213. {
  214. if (function_exists('bcpowmod')) {
  215. return bcpowmod($base, $exponent, $modulus);
  216. } else {
  217. return $this->_powmod($base, $exponent, $modulus);
  218. }
  219. }
  220. function toString($num)
  221. {
  222. return $num;
  223. }
  224. }
  225. /**
  226. * Exposes GMP math library functionality.
  227. *
  228. * {@link Auth_OpenID_GmpMathWrapper} wraps the functionality provided
  229. * by the GMP extension.
  230. *
  231. * @access private
  232. * @package OpenID
  233. */
  234. class Auth_OpenID_GmpMathWrapper extends Auth_OpenID_MathLibrary{
  235. var $type = 'gmp';
  236. function add($x, $y)
  237. {
  238. return gmp_add($x, $y);
  239. }
  240. function sub($x, $y)
  241. {
  242. return gmp_sub($x, $y);
  243. }
  244. function pow($base, $exponent)
  245. {
  246. return gmp_pow($base, $exponent);
  247. }
  248. function cmp($x, $y)
  249. {
  250. return gmp_cmp($x, $y);
  251. }
  252. function init($number, $base = 10)
  253. {
  254. return gmp_init($number, $base);
  255. }
  256. function mod($base, $modulus)
  257. {
  258. return gmp_mod($base, $modulus);
  259. }
  260. function mul($x, $y)
  261. {
  262. return gmp_mul($x, $y);
  263. }
  264. function div($x, $y)
  265. {
  266. return gmp_div_q($x, $y);
  267. }
  268. function powmod($base, $exponent, $modulus)
  269. {
  270. return gmp_powm($base, $exponent, $modulus);
  271. }
  272. function toString($num)
  273. {
  274. return gmp_strval($num);
  275. }
  276. }
  277. /**
  278. * Define the supported extensions. An extension array has keys
  279. * 'modules', 'extension', and 'class'. 'modules' is an array of PHP
  280. * module names which the loading code will attempt to load. These
  281. * values will be suffixed with a library file extension (e.g. ".so").
  282. * 'extension' is the name of a PHP extension which will be tested
  283. * before 'modules' are loaded. 'class' is the string name of a
  284. * {@link Auth_OpenID_MathWrapper} subclass which should be
  285. * instantiated if a given extension is present.
  286. *
  287. * You can define new math library implementations and add them to
  288. * this array.
  289. */
  290. function Auth_OpenID_math_extensions()
  291. {
  292. $result = array();
  293. if (!defined('Auth_OpenID_BUGGY_GMP')) {
  294. $result[] =
  295. array('modules' => array('gmp', 'php_gmp'),
  296. 'extension' => 'gmp',
  297. 'class' => 'Auth_OpenID_GmpMathWrapper');
  298. }
  299. $result[] = array('modules' => array('bcmath', 'php_bcmath'),
  300. 'extension' => 'bcmath',
  301. 'class' => 'Auth_OpenID_BcMathWrapper');
  302. return $result;
  303. }
  304. /**
  305. * Detect which (if any) math library is available
  306. */
  307. function Auth_OpenID_detectMathLibrary($exts)
  308. {
  309. $loaded = false;
  310. foreach ($exts as $extension) {
  311. if (extension_loaded($extension['extension'])) {
  312. return $extension;
  313. }
  314. }
  315. return false;
  316. }
  317. /**
  318. * {@link Auth_OpenID_getMathLib} checks for the presence of long
  319. * number extension modules and returns an instance of
  320. * {@link Auth_OpenID_MathWrapper} which exposes the module's
  321. * functionality.
  322. *
  323. * Checks for the existence of an extension module described by the
  324. * result of {@link Auth_OpenID_math_extensions()} and returns an
  325. * instance of a wrapper for that extension module. If no extension
  326. * module is found, an instance of {@link Auth_OpenID_MathWrapper} is
  327. * returned, which wraps the native PHP integer implementation. The
  328. * proper calling convention for this method is $lib =
  329. * Auth_OpenID_getMathLib().
  330. *
  331. * This function checks for the existence of specific long number
  332. * implementations in the following order: GMP followed by BCmath.
  333. *
  334. * @return Auth_OpenID_MathWrapper $instance An instance of
  335. * {@link Auth_OpenID_MathWrapper} or one of its subclasses
  336. *
  337. * @package OpenID
  338. */
  339. function Auth_OpenID_getMathLib()
  340. {
  341. // The instance of Auth_OpenID_MathWrapper that we choose to
  342. // supply will be stored here, so that subseqent calls to this
  343. // method will return a reference to the same object.
  344. static $lib = null;
  345. if (isset($lib)) {
  346. return $lib;
  347. }
  348. if (Auth_OpenID_noMathSupport()) {
  349. $null = null;
  350. return $null;
  351. }
  352. // If this method has not been called before, look at
  353. // Auth_OpenID_math_extensions and try to find an extension that
  354. // works.
  355. $ext = Auth_OpenID_detectMathLibrary(Auth_OpenID_math_extensions());
  356. if ($ext === false) {
  357. $tried = array();
  358. foreach (Auth_OpenID_math_extensions() as $extinfo) {
  359. $tried[] = $extinfo['extension'];
  360. }
  361. $triedstr = implode(", ", $tried);
  362. Auth_OpenID_setNoMathSupport();
  363. $result = null;
  364. return $result;
  365. }
  366. // Instantiate a new wrapper
  367. $class = $ext['class'];
  368. $lib = new $class();
  369. return $lib;
  370. }
  371. function Auth_OpenID_setNoMathSupport()
  372. {
  373. if (!defined('Auth_OpenID_NO_MATH_SUPPORT')) {
  374. define('Auth_OpenID_NO_MATH_SUPPORT', true);
  375. }
  376. }
  377. function Auth_OpenID_noMathSupport()
  378. {
  379. return defined('Auth_OpenID_NO_MATH_SUPPORT');
  380. }