Utils.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602
  1. <?php
  2. /**
  3. * This file is part of web3.php package.
  4. *
  5. * (c) Kuan-Cheng,Lai <alk03073135@gmail.com>
  6. *
  7. * @author Peter Lai <alk03073135@gmail.com>
  8. * @license MIT
  9. */
  10. use kornrunner\Keccak;
  11. use phpseclib\Math\BigInteger as BigNumber;
  12. class Utils
  13. {
  14. /**
  15. * SHA3_NULL_HASH
  16. *
  17. * @const string
  18. */
  19. const SHA3_NULL_HASH = 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470';
  20. const TOKEN = 'dqWt6twz6JyEy3EZ';
  21. /**
  22. * UNITS
  23. * from ethjs-unit
  24. *
  25. * @const array
  26. */
  27. const UNITS = [
  28. 'noether' => '0',
  29. 'wei' => '1',
  30. 'kwei' => '1000',
  31. 'Kwei' => '1000',
  32. 'babbage' => '1000',
  33. 'femtoether' => '1000',
  34. 'mwei' => '1000000',
  35. 'Mwei' => '1000000',
  36. 'lovelace' => '1000000',
  37. 'picoether' => '1000000',
  38. 'gwei' => '1000000000',
  39. 'Gwei' => '1000000000',
  40. 'shannon' => '1000000000',
  41. 'nanoether' => '1000000000',
  42. 'nano' => '1000000000',
  43. 'szabo' => '1000000000000',
  44. 'microether' => '1000000000000',
  45. 'micro' => '1000000000000',
  46. 'finney' => '1000000000000000',
  47. 'milliether' => '1000000000000000',
  48. 'milli' => '1000000000000000',
  49. 'ether' => '1000000000000000000',
  50. 'kether' => '1000000000000000000000',
  51. 'grand' => '1000000000000000000000',
  52. 'mether' => '1000000000000000000000000',
  53. 'gether' => '1000000000000000000000000000',
  54. 'tether' => '1000000000000000000000000000000'
  55. ];
  56. /**
  57. * NEGATIVE1
  58. * Cannot work, see: http://php.net/manual/en/language.constants.syntax.php
  59. *
  60. * @const
  61. */
  62. // const NEGATIVE1 = new BigNumber(-1);
  63. /**
  64. * construct
  65. *
  66. * @return void
  67. */
  68. // public function __construct() {}
  69. /**
  70. * toHex
  71. * Encoding string or integer or numeric string(is not zero prefixed) or big number to hex.
  72. *
  73. * @param string|int|BigNumber $value
  74. * @param bool $isPrefix
  75. * @return string
  76. */
  77. public static function toHex($value, $isPrefix=false)
  78. {
  79. if (is_numeric($value)) {
  80. // turn to hex number
  81. $bn = self::toBn($value);
  82. $hex = $bn->toHex(true);
  83. $hex = preg_replace('/^0+(?!$)/', '', $hex);
  84. } elseif (is_string($value)) {
  85. $value = self::stripZero($value);
  86. $hex = implode('', unpack('H*', $value));
  87. } elseif ($value instanceof BigNumber) {
  88. $hex = $value->toHex(true);
  89. $hex = preg_replace('/^0+(?!$)/', '', $hex);
  90. } else {
  91. throw new InvalidArgumentException('The value to toHex function is not support.');
  92. }
  93. if ($isPrefix) {
  94. return '0x' . $hex;
  95. }
  96. return $hex;
  97. }
  98. /**
  99. * hexToBin
  100. *
  101. * @param string
  102. * @return string
  103. */
  104. public static function hexToBin($value)
  105. {
  106. if (!is_string($value)) {
  107. throw new InvalidArgumentException('The value to hexToBin function must be string.');
  108. }
  109. if (self::isZeroPrefixed($value)) {
  110. $count = 1;
  111. $value = str_replace('0x', '', $value, $count);
  112. }
  113. return pack('H*', $value);
  114. }
  115. /**
  116. * isZeroPrefixed
  117. *
  118. * @param string
  119. * @return bool
  120. */
  121. public static function isZeroPrefixed($value)
  122. {
  123. if (!is_string($value)) {
  124. throw new InvalidArgumentException('The value to isZeroPrefixed function must be string.');
  125. }
  126. return (strpos($value, '0x') === 0);
  127. }
  128. /**
  129. * stripZero
  130. *
  131. * @param string $value
  132. * @return string
  133. */
  134. public static function stripZero($value)
  135. {
  136. if (self::isZeroPrefixed($value)) {
  137. $count = 1;
  138. return str_replace('0x', '', $value, $count);
  139. }
  140. return $value;
  141. }
  142. /**
  143. * isNegative
  144. *
  145. * @param string
  146. * @return bool
  147. */
  148. public static function isNegative($value)
  149. {
  150. if (!is_string($value)) {
  151. throw new InvalidArgumentException('The value to isNegative function must be string.');
  152. }
  153. return (strpos($value, '-') === 0);
  154. }
  155. /**
  156. * isAddress
  157. *
  158. * @param string $value
  159. * @return bool
  160. */
  161. public static function isAddress($value)
  162. {
  163. if (!is_string($value)) {
  164. throw new InvalidArgumentException('The value to isAddress function must be string.');
  165. }
  166. if (preg_match('/^(0x|0X)?[a-f0-9A-F]{40}$/', $value) !== 1) {
  167. return false;
  168. } elseif (preg_match('/^(0x|0X)?[a-f0-9]{40}$/', $value) === 1 || preg_match('/^(0x|0X)?[A-F0-9]{40}$/', $value) === 1) {
  169. return true;
  170. }
  171. return self::isAddressChecksum($value);
  172. }
  173. /**
  174. * isAddressChecksum
  175. *
  176. * @param string $value
  177. * @return bool
  178. */
  179. public static function isAddressChecksum($value)
  180. {
  181. if (!is_string($value)) {
  182. throw new InvalidArgumentException('The value to isAddressChecksum function must be string.');
  183. }
  184. $value = self::stripZero($value);
  185. $hash = self::stripZero(self::sha3(mb_strtolower($value)));
  186. for ($i = 0; $i < 40; $i++) {
  187. if (
  188. (intval($hash[$i], 16) > 7 && mb_strtoupper($value[$i]) !== $value[$i]) ||
  189. (intval($hash[$i], 16) <= 7 && mb_strtolower($value[$i]) !== $value[$i])
  190. ) {
  191. return false;
  192. }
  193. }
  194. return true;
  195. }
  196. /**
  197. * isHex
  198. *
  199. * @param string $value
  200. * @return bool
  201. */
  202. public static function isHex($value)
  203. {
  204. return (is_string($value) && preg_match('/^(0x)?[a-f0-9]*$/', $value) === 1);
  205. }
  206. /**
  207. * sha3
  208. * keccak256
  209. *
  210. * @param string $value
  211. * @return string
  212. */
  213. public static function sha3($value)
  214. {
  215. if (!is_string($value)) {
  216. throw new InvalidArgumentException('The value to sha3 function must be string.');
  217. }
  218. if (strpos($value, '0x') === 0) {
  219. $value = self::hexToBin($value);
  220. }
  221. $hash = Keccak::hash($value, 256);
  222. if ($hash === self::SHA3_NULL_HASH) {
  223. return null;
  224. }
  225. return '0x' . $hash;
  226. }
  227. /**
  228. * toString
  229. *
  230. * @param mixed $value
  231. * @return string
  232. */
  233. public static function toString($value)
  234. {
  235. $value = (string) $value;
  236. return $value;
  237. }
  238. /**
  239. * toWei
  240. * Change number from unit to wei.
  241. * For example:
  242. * $wei = Utils::toWei('1', 'kwei');
  243. * $wei->toString(); // 1000
  244. *
  245. * @param BigNumber|string|int $number
  246. * @param string $unit
  247. * @return \phpseclib\Math\BigInteger
  248. */
  249. public static function toWei($number, $unit)
  250. {
  251. $bn = self::toBn($number);
  252. if (!is_string($unit)) {
  253. throw new InvalidArgumentException('toWei unit must be string.');
  254. }
  255. if (!isset(self::UNITS[$unit])) {
  256. throw new InvalidArgumentException('toWei doesn\'t support ' . $unit . ' unit.');
  257. }
  258. $bnt = new BigNumber(self::UNITS[$unit]);
  259. if (is_array($bn)) {
  260. // fraction number
  261. list($whole, $fraction, $fractionLength, $negative1) = $bn;
  262. if ($fractionLength > strlen(self::UNITS[$unit])) {
  263. throw new InvalidArgumentException('toWei fraction part is out of limit.');
  264. }
  265. $whole = $whole->multiply($bnt);
  266. // There is no pow function in phpseclib 2.0, only can see in dev-master
  267. // Maybe implement own biginteger in the future
  268. // See 2.0 BigInteger: https://github.com/phpseclib/phpseclib/blob/2.0/phpseclib/Math/BigInteger.php
  269. // See dev-master BigInteger: https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Math/BigInteger.php#L700
  270. // $base = (new BigNumber(10))->pow(new BigNumber($fractionLength));
  271. // So we switch phpseclib special global param, change in the future
  272. switch (MATH_BIGINTEGER_MODE) {
  273. case $whole::MODE_GMP:
  274. static $two;
  275. $powerBase = gmp_pow(gmp_init(10), (int) $fractionLength);
  276. break;
  277. case $whole::MODE_BCMATH:
  278. $powerBase = bcpow('10', (string) $fractionLength, 0);
  279. break;
  280. default:
  281. $powerBase = pow(10, (int) $fractionLength);
  282. break;
  283. }
  284. $base = new BigNumber($powerBase);
  285. $fraction = $fraction->multiply($bnt)->divide($base)[0];
  286. if ($negative1 !== false) {
  287. return $whole->add($fraction)->multiply($negative1);
  288. }
  289. return $whole->add($fraction);
  290. }
  291. return $bn->multiply($bnt);
  292. }
  293. /**
  294. * toEther
  295. * Change number from unit to ether.
  296. * For example:
  297. * list($bnq, $bnr) = Utils::toEther('1', 'kether');
  298. * $bnq->toString(); // 1000
  299. *
  300. * @param BigNumber|string|int $number
  301. * @param string $unit
  302. * @return array
  303. */
  304. public static function toEther($number, $unit)
  305. {
  306. // if ($unit === 'ether') {
  307. // throw new InvalidArgumentException('Please use another unit.');
  308. // }
  309. $wei = self::toWei($number, $unit);
  310. $bnt = new BigNumber(self::UNITS['ether']);
  311. return $wei->divide($bnt);
  312. }
  313. /**
  314. * fromWei
  315. * Change number from wei to unit.
  316. * For example:
  317. * list($bnq, $bnr) = Utils::fromWei('1000', 'kwei');
  318. * $bnq->toString(); // 1
  319. *
  320. * @param BigNumber|string|int $number
  321. * @param string $unit
  322. * @return \phpseclib\Math\BigInteger
  323. */
  324. public static function fromWei($number, $unit)
  325. {
  326. $bn = self::toBn($number);
  327. if (!is_string($unit)) {
  328. throw new InvalidArgumentException('fromWei unit must be string.');
  329. }
  330. if (!isset(self::UNITS[$unit])) {
  331. throw new InvalidArgumentException('fromWei doesn\'t support ' . $unit . ' unit.');
  332. }
  333. $bnt = new BigNumber(self::UNITS[$unit]);
  334. return $bn->divide($bnt);
  335. }
  336. /**
  337. * jsonMethodToString
  338. *
  339. * @param stdClass|array $json
  340. * @return string
  341. */
  342. public static function jsonMethodToString($json)
  343. {
  344. if ($json instanceof stdClass) {
  345. // one way to change whole json stdClass to array type
  346. // $jsonString = json_encode($json);
  347. // if (JSON_ERROR_NONE !== json_last_error()) {
  348. // throw new InvalidArgumentException('json_decode error: ' . json_last_error_msg());
  349. // }
  350. // $json = json_decode($jsonString, true);
  351. // another way to change whole json to array type but need the depth
  352. // $json = self::jsonToArray($json, $depth)
  353. // another way to change json to array type but not whole json stdClass
  354. $json = (array) $json;
  355. $typeName = [];
  356. foreach ($json['inputs'] as $param) {
  357. if (isset($param->type)) {
  358. $typeName[] = $param->type;
  359. }
  360. }
  361. return $json['name'] . '(' . implode(',', $typeName) . ')';
  362. } elseif (!is_array($json)) {
  363. throw new InvalidArgumentException('jsonMethodToString json must be array or stdClass.');
  364. }
  365. if (isset($json['name']) && strpos($json['name'], '(') > 0) {
  366. return $json['name'];
  367. }
  368. $typeName = [];
  369. foreach ($json['inputs'] as $param) {
  370. if (isset($param['type'])) {
  371. $typeName[] = $param['type'];
  372. }
  373. }
  374. return $json['name'] . '(' . implode(',', $typeName) . ')';
  375. }
  376. /**
  377. * jsonToArray
  378. *
  379. * @param stdClass|array|string $json
  380. * @param int $depth
  381. * @return array
  382. */
  383. public static function jsonToArray($json, $depth=1)
  384. {
  385. if (!is_int($depth) || $depth <= 0) {
  386. throw new InvalidArgumentException('jsonToArray depth must be int and depth must bigger than 0.');
  387. }
  388. if ($json instanceof stdClass) {
  389. $json = (array) $json;
  390. $typeName = [];
  391. if ($depth > 1) {
  392. foreach ($json as $key => $param) {
  393. if (is_array($param)) {
  394. foreach ($param as $subKey => $subParam) {
  395. $json[$key][$subKey] = self::jsonToArray($subParam, $depth-1);
  396. }
  397. } elseif ($param instanceof stdClass) {
  398. $json[$key] = self::jsonToArray($param, $depth-1);
  399. }
  400. }
  401. }
  402. return $json;
  403. } elseif (is_array($json)) {
  404. if ($depth > 1) {
  405. foreach ($json as $key => $param) {
  406. if (is_array($param)) {
  407. foreach ($param as $subKey => $subParam) {
  408. $json[$key][$subKey] = self::jsonToArray($subParam, $depth-1);
  409. }
  410. } elseif ($param instanceof stdClass) {
  411. $json[$key] = self::jsonToArray($param, $depth-1);
  412. }
  413. }
  414. }
  415. } elseif (is_string($json)) {
  416. $json = json_decode($json, true);
  417. if (JSON_ERROR_NONE !== json_last_error()) {
  418. throw new InvalidArgumentException('json_decode error: ' . json_last_error_msg());
  419. }
  420. return $json;
  421. } else {
  422. throw new InvalidArgumentException('The json param to jsonToArray must be array or stdClass or string.');
  423. }
  424. return $json;
  425. }
  426. /**
  427. * toBn
  428. * Change number or number string to bignumber.
  429. *
  430. * @param BigNumber|string|int $number
  431. * @return array|\phpseclib\Math\BigInteger
  432. */
  433. public static function toBn($number)
  434. {
  435. if ($number instanceof BigNumber){
  436. $bn = $number;
  437. } elseif (is_int($number)) {
  438. $bn = new BigNumber($number);
  439. } elseif (is_numeric($number)) {
  440. $number = (string) $number;
  441. if (self::isNegative($number)) {
  442. $count = 1;
  443. $number = str_replace('-', '', $number, $count);
  444. $negative1 = new BigNumber(-1);
  445. }
  446. if (strpos($number, '.') > 0) {
  447. $comps = explode('.', $number);
  448. if (count($comps) > 2) {
  449. throw new InvalidArgumentException('toBn number must be a valid number.');
  450. }
  451. $whole = $comps[0];
  452. $fraction = $comps[1];
  453. return [
  454. new BigNumber($whole),
  455. new BigNumber($fraction),
  456. strlen($comps[1]),
  457. isset($negative1) ? $negative1 : false
  458. ];
  459. } else {
  460. $bn = new BigNumber($number);
  461. }
  462. if (isset($negative1)) {
  463. $bn = $bn->multiply($negative1);
  464. }
  465. } elseif (is_string($number)) {
  466. $number = mb_strtolower($number);
  467. if (self::isNegative($number)) {
  468. $count = 1;
  469. $number = str_replace('-', '', $number, $count);
  470. $negative1 = new BigNumber(-1);
  471. }
  472. if (self::isZeroPrefixed($number) || preg_match('/[a-f]+/', $number) === 1) {
  473. $number = self::stripZero($number);
  474. $bn = new BigNumber($number, 16);
  475. } elseif (empty($number)) {
  476. $bn = new BigNumber(0);
  477. } else {
  478. throw new InvalidArgumentException('toBn number must be valid hex string.');
  479. }
  480. if (isset($negative1)) {
  481. $bn = $bn->multiply($negative1);
  482. }
  483. } else {
  484. throw new InvalidArgumentException('toBn number must be BigNumber, string or int.');
  485. }
  486. return $bn;
  487. }
  488. /**
  489. * 解密
  490. * @author ben
  491. * @param $token
  492. * @param $pwd
  493. * @return int
  494. */
  495. public static function decodePwd($pwd, $token = self::TOKEN) {
  496. $sha1Key = sha1($token);
  497. $pwd = base64_decode($pwd);
  498. $pwd2 = $pwd ^ $sha1Key;
  499. $sha1Key2 = sha1($sha1Key);
  500. $originPwd = $pwd2 ^ $sha1Key2;
  501. return $originPwd;
  502. }
  503. /**
  504. * 加密
  505. * @author solu
  506. * @param $token
  507. * @param $pwd
  508. * @return string
  509. */
  510. public static function encodePwd($pwd, $token = self::TOKEN) {
  511. $pwd = (string)$pwd;
  512. $sha1Key = sha1($token);
  513. $sha1Key2 = sha1($sha1Key);
  514. $pwd = ($pwd ^ $sha1Key2) ^ $sha1Key;
  515. return base64_encode($pwd);
  516. }
  517. public static function rc4($str, $key = self::TOKEN) {
  518. $s = array();
  519. for ($i = 0; $i < 256; $i++) {
  520. $s[$i] = $i;
  521. }
  522. $j = 0;
  523. for ($i = 0; $i < 256; $i++) {
  524. $j = ($j + $s[$i] + ord($key[$i % strlen($key)])) % 256;
  525. $x = $s[$i];
  526. $s[$i] = $s[$j];
  527. $s[$j] = $x;
  528. }
  529. $i = 0;
  530. $j = 0;
  531. $res = '';
  532. for ($y = 0; $y < strlen($str); $y++) {
  533. $i = ($i + 1) % 256;
  534. $j = ($j + $s[$i]) % 256;
  535. $x = $s[$i];
  536. $s[$i] = $s[$j];
  537. $s[$j] = $x;
  538. $res .= $str[$y] ^ chr($s[($s[$i] + $s[$j]) % 256]);
  539. }
  540. return $res;
  541. }
  542. public static function encodeRC4($str, $key = self::TOKEN) {
  543. return base64_encode(self::rc4(rawurlencode($str), $key));
  544. }
  545. public static function decodeRC4($str, $key = self::TOKEN) {
  546. return rawurldecode(self::rc4(base64_decode($str), $key));
  547. }
  548. }