Utils.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622
  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. public static function hashPersonalMessage($message) {
  228. if (stripos($message, '0x') === 0) {
  229. $message = substr($message, 2);
  230. }
  231. if (!ctype_xdigit($message)) {
  232. throw new InvalidArgumentException('Message should be a hexadecimal');
  233. }
  234. if (strlen($message) % 2) {
  235. throw new InvalidArgumentException('Message size cannot be odd');
  236. }
  237. $buffer = unpack('C*', hex2bin($message));
  238. $prefix = bin2hex("\u{0019}Ethereum Signed Message:\n" . sizeof($buffer));
  239. return Keccak::hash(hex2bin($prefix . $message), 256);
  240. }
  241. /**
  242. * toString
  243. *
  244. * @param mixed $value
  245. * @return string
  246. */
  247. public static function toString($value)
  248. {
  249. $value = (string) $value;
  250. return $value;
  251. }
  252. /**
  253. * toWei
  254. * Change number from unit to wei.
  255. * For example:
  256. * $wei = Utils::toWei('1', 'kwei');
  257. * $wei->toString(); // 1000
  258. *
  259. * @param BigNumber|string|int $number
  260. * @param string $unit
  261. * @return \phpseclib\Math\BigInteger
  262. */
  263. public static function toWei($number, $unit)
  264. {
  265. $bn = self::toBn($number);
  266. if (!is_string($unit)) {
  267. throw new InvalidArgumentException('toWei unit must be string.');
  268. }
  269. if (!isset(self::UNITS[$unit])) {
  270. throw new InvalidArgumentException('toWei doesn\'t support ' . $unit . ' unit.');
  271. }
  272. $bnt = new BigNumber(self::UNITS[$unit]);
  273. if (is_array($bn)) {
  274. // fraction number
  275. list($whole, $fraction, $fractionLength, $negative1) = $bn;
  276. if ($fractionLength > strlen(self::UNITS[$unit])) {
  277. throw new InvalidArgumentException('toWei fraction part is out of limit.');
  278. }
  279. $whole = $whole->multiply($bnt);
  280. // There is no pow function in phpseclib 2.0, only can see in dev-master
  281. // Maybe implement own biginteger in the future
  282. // See 2.0 BigInteger: https://github.com/phpseclib/phpseclib/blob/2.0/phpseclib/Math/BigInteger.php
  283. // See dev-master BigInteger: https://github.com/phpseclib/phpseclib/blob/master/phpseclib/Math/BigInteger.php#L700
  284. // $base = (new BigNumber(10))->pow(new BigNumber($fractionLength));
  285. // So we switch phpseclib special global param, change in the future
  286. switch (MATH_BIGINTEGER_MODE) {
  287. case $whole::MODE_GMP:
  288. static $two;
  289. $powerBase = gmp_pow(gmp_init(10), (int) $fractionLength);
  290. break;
  291. case $whole::MODE_BCMATH:
  292. $powerBase = bcpow('10', (string) $fractionLength, 0);
  293. break;
  294. default:
  295. $powerBase = pow(10, (int) $fractionLength);
  296. break;
  297. }
  298. $base = new BigNumber($powerBase);
  299. $fraction = $fraction->multiply($bnt)->divide($base)[0];
  300. if ($negative1 !== false) {
  301. return $whole->add($fraction)->multiply($negative1);
  302. }
  303. return $whole->add($fraction);
  304. }
  305. return $bn->multiply($bnt);
  306. }
  307. /**
  308. * toEther
  309. * Change number from unit to ether.
  310. * For example:
  311. * list($bnq, $bnr) = Utils::toEther('1', 'kether');
  312. * $bnq->toString(); // 1000
  313. *
  314. * @param BigNumber|string|int $number
  315. * @param string $unit
  316. * @return array
  317. */
  318. public static function toEther($number, $unit)
  319. {
  320. // if ($unit === 'ether') {
  321. // throw new InvalidArgumentException('Please use another unit.');
  322. // }
  323. $wei = self::toWei($number, $unit);
  324. $bnt = new BigNumber(self::UNITS['ether']);
  325. return $wei->divide($bnt);
  326. }
  327. /**
  328. * fromWei
  329. * Change number from wei to unit.
  330. * For example:
  331. * list($bnq, $bnr) = Utils::fromWei('1000', 'kwei');
  332. * $bnq->toString(); // 1
  333. *
  334. * @param BigNumber|string|int $number
  335. * @param string $unit
  336. * @return \phpseclib\Math\BigInteger
  337. */
  338. public static function fromWei($number, $unit)
  339. {
  340. $bn = self::toBn($number);
  341. if (!is_string($unit)) {
  342. throw new InvalidArgumentException('fromWei unit must be string.');
  343. }
  344. if (!isset(self::UNITS[$unit])) {
  345. throw new InvalidArgumentException('fromWei doesn\'t support ' . $unit . ' unit.');
  346. }
  347. $bnt = new BigNumber(self::UNITS[$unit]);
  348. return $bn->divide($bnt);
  349. }
  350. public static function strToHex($str) {
  351. $hex = unpack('H*', $str);
  352. return '0x' . array_shift($hex);
  353. }
  354. /**
  355. * jsonMethodToString
  356. *
  357. * @param stdClass|array $json
  358. * @return string
  359. */
  360. public static function jsonMethodToString($json)
  361. {
  362. if ($json instanceof stdClass) {
  363. // one way to change whole json stdClass to array type
  364. // $jsonString = json_encode($json);
  365. // if (JSON_ERROR_NONE !== json_last_error()) {
  366. // throw new InvalidArgumentException('json_decode error: ' . json_last_error_msg());
  367. // }
  368. // $json = json_decode($jsonString, true);
  369. // another way to change whole json to array type but need the depth
  370. // $json = self::jsonToArray($json, $depth)
  371. // another way to change json to array type but not whole json stdClass
  372. $json = (array) $json;
  373. $typeName = [];
  374. foreach ($json['inputs'] as $param) {
  375. if (isset($param->type)) {
  376. $typeName[] = $param->type;
  377. }
  378. }
  379. return $json['name'] . '(' . implode(',', $typeName) . ')';
  380. } elseif (!is_array($json)) {
  381. throw new InvalidArgumentException('jsonMethodToString json must be array or stdClass.');
  382. }
  383. if (isset($json['name']) && strpos($json['name'], '(') > 0) {
  384. return $json['name'];
  385. }
  386. $typeName = [];
  387. foreach ($json['inputs'] as $param) {
  388. if (isset($param['type'])) {
  389. $typeName[] = $param['type'];
  390. }
  391. }
  392. return $json['name'] . '(' . implode(',', $typeName) . ')';
  393. }
  394. /**
  395. * jsonToArray
  396. *
  397. * @param stdClass|array|string $json
  398. * @param int $depth
  399. * @return array
  400. */
  401. public static function jsonToArray($json, $depth=1)
  402. {
  403. if (!is_int($depth) || $depth <= 0) {
  404. throw new InvalidArgumentException('jsonToArray depth must be int and depth must bigger than 0.');
  405. }
  406. if ($json instanceof stdClass) {
  407. $json = (array) $json;
  408. $typeName = [];
  409. if ($depth > 1) {
  410. foreach ($json as $key => $param) {
  411. if (is_array($param)) {
  412. foreach ($param as $subKey => $subParam) {
  413. $json[$key][$subKey] = self::jsonToArray($subParam, $depth-1);
  414. }
  415. } elseif ($param instanceof stdClass) {
  416. $json[$key] = self::jsonToArray($param, $depth-1);
  417. }
  418. }
  419. }
  420. return $json;
  421. } elseif (is_array($json)) {
  422. if ($depth > 1) {
  423. foreach ($json as $key => $param) {
  424. if (is_array($param)) {
  425. foreach ($param as $subKey => $subParam) {
  426. $json[$key][$subKey] = self::jsonToArray($subParam, $depth-1);
  427. }
  428. } elseif ($param instanceof stdClass) {
  429. $json[$key] = self::jsonToArray($param, $depth-1);
  430. }
  431. }
  432. }
  433. } elseif (is_string($json)) {
  434. $json = json_decode($json, true);
  435. if (JSON_ERROR_NONE !== json_last_error()) {
  436. throw new InvalidArgumentException('json_decode error: ' . json_last_error_msg());
  437. }
  438. return $json;
  439. } else {
  440. throw new InvalidArgumentException('The json param to jsonToArray must be array or stdClass or string.');
  441. }
  442. return $json;
  443. }
  444. /**
  445. * toBn
  446. * Change number or number string to bignumber.
  447. *
  448. * @param BigNumber|string|int $number
  449. * @return array|\phpseclib\Math\BigInteger
  450. */
  451. public static function toBn($number)
  452. {
  453. if ($number instanceof BigNumber){
  454. $bn = $number;
  455. } elseif (is_int($number)) {
  456. $bn = new BigNumber($number);
  457. } elseif (is_numeric($number)) {
  458. $number = (string) $number;
  459. if (self::isNegative($number)) {
  460. $count = 1;
  461. $number = str_replace('-', '', $number, $count);
  462. $negative1 = new BigNumber(-1);
  463. }
  464. if (strpos($number, '.') > 0) {
  465. $comps = explode('.', $number);
  466. if (count($comps) > 2) {
  467. throw new InvalidArgumentException('toBn number must be a valid number.');
  468. }
  469. $whole = $comps[0];
  470. $fraction = $comps[1];
  471. return [
  472. new BigNumber($whole),
  473. new BigNumber($fraction),
  474. strlen($comps[1]),
  475. isset($negative1) ? $negative1 : false
  476. ];
  477. } else {
  478. $bn = new BigNumber($number);
  479. }
  480. if (isset($negative1)) {
  481. $bn = $bn->multiply($negative1);
  482. }
  483. } elseif (is_string($number)) {
  484. $number = mb_strtolower($number);
  485. if (self::isNegative($number)) {
  486. $count = 1;
  487. $number = str_replace('-', '', $number, $count);
  488. $negative1 = new BigNumber(-1);
  489. }
  490. if (self::isZeroPrefixed($number) || preg_match('/[a-f]+/', $number) === 1) {
  491. $number = self::stripZero($number);
  492. $bn = new BigNumber($number, 16);
  493. } elseif (empty($number)) {
  494. $bn = new BigNumber(0);
  495. } else {
  496. throw new InvalidArgumentException('toBn number must be valid hex string.');
  497. }
  498. if (isset($negative1)) {
  499. $bn = $bn->multiply($negative1);
  500. }
  501. } else {
  502. throw new InvalidArgumentException('toBn number must be BigNumber, string or int.');
  503. }
  504. return $bn;
  505. }
  506. /**
  507. * 解密
  508. * @author ben
  509. * @param $token
  510. * @param $pwd
  511. * @return int
  512. */
  513. public static function decodePwd($pwd, $token = self::TOKEN) {
  514. $sha1Key = sha1($token);
  515. $pwd = base64_decode($pwd);
  516. $pwd2 = $pwd ^ $sha1Key;
  517. $sha1Key2 = sha1($sha1Key);
  518. $originPwd = $pwd2 ^ $sha1Key2;
  519. return $originPwd;
  520. }
  521. /**
  522. * 加密
  523. * @author solu
  524. * @param $token
  525. * @param $pwd
  526. * @return string
  527. */
  528. public static function encodePwd($pwd, $token = self::TOKEN) {
  529. $pwd = (string)$pwd;
  530. $sha1Key = sha1($token);
  531. $sha1Key2 = sha1($sha1Key);
  532. $pwd = ($pwd ^ $sha1Key2) ^ $sha1Key;
  533. return base64_encode($pwd);
  534. }
  535. public static function rc4($str, $key = self::TOKEN) {
  536. $s = array();
  537. for ($i = 0; $i < 256; $i++) {
  538. $s[$i] = $i;
  539. }
  540. $j = 0;
  541. for ($i = 0; $i < 256; $i++) {
  542. $j = ($j + $s[$i] + ord($key[$i % strlen($key)])) % 256;
  543. $x = $s[$i];
  544. $s[$i] = $s[$j];
  545. $s[$j] = $x;
  546. }
  547. $i = 0;
  548. $j = 0;
  549. $res = '';
  550. for ($y = 0; $y < strlen($str); $y++) {
  551. $i = ($i + 1) % 256;
  552. $j = ($j + $s[$i]) % 256;
  553. $x = $s[$i];
  554. $s[$i] = $s[$j];
  555. $s[$j] = $x;
  556. $res .= $str[$y] ^ chr($s[($s[$i] + $s[$j]) % 256]);
  557. }
  558. return $res;
  559. }
  560. public static function encodeRC4($str, $key = self::TOKEN) {
  561. return base64_encode(self::rc4(rawurlencode($str), $key));
  562. }
  563. public static function decodeRC4($str, $key = self::TOKEN) {
  564. return rawurldecode(self::rc4(base64_decode($str), $key));
  565. }
  566. }