Signature.php 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. <?php
  2. /*
  3. * Crypto Currency Message Signing and Verification
  4. * For Bitcoin/Zetacoin compatable Crypto Currency utilizing the secp256k1 curve
  5. * @author Daniel Morante
  6. * Some parts may contain work based on Jan Moritz Lindemann, Matyas Danter, and Joey Hewitt
  7. */
  8. class Signature {
  9. /***
  10. * Sign a hash with the private key that was set and returns signatures as an array (R,S)
  11. *
  12. * @param $hash
  13. * @param $k
  14. * @param null $nonce
  15. * @throws \Exception
  16. * @return Array
  17. */
  18. public static function getSignatureHashPoints($hash, $k, $nonce = null)
  19. {
  20. $secp256k1 = new SECp256k1();
  21. $a = $secp256k1->a;
  22. $b = $secp256k1->b;
  23. $G = $secp256k1->G;
  24. $n = $secp256k1->n;
  25. $p = $secp256k1->p;
  26. if(empty($k))
  27. {
  28. throw new \Exception('No Private Key was defined');
  29. }
  30. if(null == $nonce)
  31. {
  32. $random = openssl_random_pseudo_bytes(256, $cStrong);
  33. $random = $random . microtime(true).rand(100000000000, 1000000000000);
  34. $nonce = gmp_strval(gmp_mod(gmp_init(hash('sha256',$random), 16), $n), 16);
  35. }
  36. //first part of the signature (R).
  37. $rPt = PointMathGMP::mulPoint($nonce, $G, $a, $b, $p);
  38. $R = gmp_strval($rPt ['x'], 16);
  39. while(strlen($R) < 64)
  40. {
  41. $R = '0' . $R;
  42. }
  43. //second part of the signature (S).
  44. //S = nonce^-1 (hash + privKey * R) mod p
  45. $S = gmp_strval(
  46. gmp_mod(
  47. gmp_mul(
  48. gmp_invert(
  49. gmp_init($nonce, 16),
  50. $n
  51. ),
  52. gmp_add(
  53. gmp_init($hash, 16),
  54. gmp_mul(
  55. gmp_init($k, 16),
  56. gmp_init($R, 16)
  57. )
  58. )
  59. ),
  60. $n
  61. ),
  62. 16
  63. );
  64. if(strlen($S)%2)
  65. {
  66. $S = '0' . $S;
  67. }
  68. if(strlen($R)%2)
  69. {
  70. $R = '0' . $R;
  71. }
  72. return array('R' => $R, 'S' => $S);
  73. }
  74. /***
  75. * Sign a hash with the private key that was set and returns a DER encoded signature
  76. *
  77. * @param $hash
  78. * @param null $nonce
  79. * @return string
  80. */
  81. public static function signHash($hash, $k, $nonce = null)
  82. {
  83. $points = self::getSignatureHashPoints($hash, $k, $nonce);
  84. $signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S'];
  85. $signature = '30' . dechex(strlen(hex2bin($signature))) . $signature;
  86. return $signature;
  87. }
  88. /***
  89. * extract the public key from the signature and using the recovery flag.
  90. * see http://crypto.stackexchange.com/a/18106/10927
  91. * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js
  92. * possible public keys are r−1(sR−zG) and r−1(sR′−zG)
  93. * Recovery flag rules are :
  94. * binary number between 28 and 35 inclusive
  95. * if the flag is > 30 then the address is compressed.
  96. *
  97. * @param $flag (INT)
  98. * @param $R (HEX String)
  99. * @param $S (HEX String)
  100. * @param $hash (HEX String)
  101. * @return array
  102. */
  103. public static function getPubKeyWithRS($flag, $R, $S, $hash)
  104. {
  105. $secp256k1 = new SECp256k1();
  106. $a = $secp256k1->a;
  107. $b = $secp256k1->b;
  108. $G = $secp256k1->G;
  109. $n = $secp256k1->n;
  110. $p = $secp256k1->p;
  111. $isCompressed = false;
  112. if ($flag < 27 || $flag >= 35) {
  113. return false;
  114. }
  115. if($flag >= 31) //if address is compressed
  116. {
  117. $isCompressed = true;
  118. $flag -= 4;
  119. }
  120. $recid = $flag - 27;
  121. //step 1.1
  122. $x = null;
  123. $x = gmp_add(
  124. gmp_init($R, 16),
  125. gmp_mul(
  126. $n,
  127. gmp_div_q( //check if j is equal to 0 or to 1.
  128. gmp_init($recid, 10),
  129. gmp_init(2, 10)
  130. )
  131. )
  132. );
  133. //step 1.3
  134. $y = null;
  135. if(1 == $flag % 2) //check if y is even.
  136. {
  137. $gmpY = PointMathGMP::calculateYWithX(gmp_strval($x, 16), $a, $b, $p, '02');
  138. if(null != $gmpY)
  139. $y = gmp_init($gmpY, 16);
  140. }
  141. else
  142. {
  143. $gmpY = PointMathGMP::calculateYWithX(gmp_strval($x, 16), $a, $b, $p, '03');
  144. if(null != $gmpY)
  145. $y = gmp_init($gmpY, 16);
  146. }
  147. if(null == $y)
  148. return null;
  149. $Rpt = array('x' => $x, 'y' => $y);
  150. //step 1.6.1
  151. //calculate r^-1 (S*Rpt - eG)
  152. $eG = PointMathGMP::mulPoint($hash, $G, $a, $b, $p);
  153. $Rinv = gmp_strval(gmp_invert(gmp_init($R, 16), $n), 16);
  154. // Possible issue
  155. $eG['y'] = gmp_mod(gmp_neg($eG['y']), $p);
  156. // Possible Fix
  157. //$eG['y'] = gmp_neg($eG['y']);
  158. $SR = PointMathGMP::mulPoint($S, $Rpt, $a, $b, $p);
  159. $sR_plus_eGNeg = PointMathGMP::addPoints($SR, $eG, $a, $p);
  160. $pubKey = PointMathGMP::mulPoint(
  161. $Rinv,
  162. $sR_plus_eGNeg,
  163. $a,
  164. $b,
  165. $p
  166. );
  167. $pubKey['x'] = gmp_strval($pubKey['x'], 16);
  168. $pubKey['y'] = gmp_strval($pubKey['y'], 16);
  169. while(strlen($pubKey['x']) < 64)
  170. $pubKey['x'] = '0' . $pubKey['x'];
  171. while(strlen($pubKey['y']) < 64)
  172. $pubKey['y'] = '0' . $pubKey['y'];
  173. if($isCompressed){
  174. $derPubKey = AddressCodec::Compress($pubKey);
  175. }
  176. else{
  177. $derPubKey = AddressCodec::Hex($pubKey);
  178. }
  179. if(self::checkSignaturePoints($derPubKey, $R, $S, $hash)){
  180. return $derPubKey;
  181. }
  182. else{
  183. return false;
  184. }
  185. }
  186. // Same as Below but accepts HEX strings
  187. public static function recoverPublicKey_HEX($flag, $R, $S, $hash){
  188. return self::recoverPublicKey(gmp_init($R,16), gmp_init($S,16), gmp_init($hash,16), $flag);
  189. }
  190. // $R, $S, and $hash are GMP
  191. // $recoveryFlags is INT
  192. public static function recoverPublicKey($R, $S, $hash, $recoveryFlags){
  193. $secp256k1 = new SECp256k1();
  194. $a = $secp256k1->a;
  195. $b = $secp256k1->b;
  196. $G = $secp256k1->G;
  197. $n = $secp256k1->n;
  198. $p = $secp256k1->p;
  199. $isYEven = ($recoveryFlags & 1) != 0;
  200. $isSecondKey = ($recoveryFlags & 2) != 0;
  201. // PointMathGMP::mulPoint wants HEX String
  202. $e = gmp_strval($hash, 16);
  203. $s = gmp_strval($S, 16);
  204. // Precalculate (p + 1) / 4 where p is the field order
  205. // $p_over_four is GMP
  206. static $p_over_four; // XXX just assuming only one curve/prime will be used
  207. if (!$p_over_four) {
  208. $p_over_four = gmp_div(gmp_add($p, 1), 4);
  209. }
  210. // 1.1 Compute x
  211. // $x is GMP
  212. if (!$isSecondKey) {
  213. $x = $R;
  214. } else {
  215. $x = gmp_add($R, $n);
  216. }
  217. // 1.3 Convert x to point
  218. // $alpha is GMP
  219. $alpha = gmp_mod(gmp_add(gmp_add(gmp_pow($x, 3), gmp_mul($a, $x)), $b), $p);
  220. // $beta is DEC String (INT)
  221. $beta = gmp_strval(gmp_powm($alpha, $p_over_four, $p));
  222. // If beta is even, but y isn't or vice versa, then convert it,
  223. // otherwise we're done and y == beta.
  224. if (PointMathGMP::isEvenNumber($beta) == $isYEven) {
  225. // gmp_sub function will convert the DEC String "$beta" into a GMP
  226. // $y is a GMP
  227. $y = gmp_sub($p, $beta);
  228. } else {
  229. // $y is a GMP
  230. $y = gmp_init($beta);
  231. }
  232. // 1.4 Check that nR is at infinity (implicitly done in construtor) -- Not reallly
  233. // $Rpt is Array(GMP, GMP)
  234. $Rpt = array('x' => $x, 'y' => $y);
  235. // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
  236. // $rInv is a HEX String
  237. $rInv = gmp_strval(gmp_invert($R, $n), 16);
  238. // $eGNeg is Array (GMP, GMP)
  239. $eGNeg = PointMathGMP::negatePoint(PointMathGMP::mulPoint($e, $G, $a, $b, $p));
  240. $sR = PointMathGMP::mulPoint($s, $Rpt, $a, $b, $p);
  241. $sR_plus_eGNeg = PointMathGMP::addPoints($sR, $eGNeg, $a, $p);
  242. // $Q is Array (GMP, GMP)
  243. $Q = PointMathGMP::mulPoint($rInv, $sR_plus_eGNeg, $a, $b, $p);
  244. // Q is the derrived public key
  245. // $pubkey is Array (HEX String, HEX String)
  246. // Ensure it's always 64 HEX Charaters
  247. $pubKey['x'] = str_pad(gmp_strval($Q['x'], 16), 64, 0, STR_PAD_LEFT);
  248. $pubKey['y'] = str_pad(gmp_strval($Q['y'], 16), 64, 0, STR_PAD_LEFT);
  249. return $pubKey;
  250. }
  251. /***
  252. * Check signature with public key R & S values of the signature and the message hash.
  253. *
  254. * @param $pubKey
  255. * @param $R
  256. * @param $S
  257. * @param $hash
  258. * @return bool
  259. */
  260. public static function checkSignaturePoints($pubKey, $R, $S, $hash)
  261. {
  262. $secp256k1 = new SECp256k1();
  263. $a = $secp256k1->a;
  264. $b = $secp256k1->b;
  265. $G = $secp256k1->G;
  266. $n = $secp256k1->n;
  267. $p = $secp256k1->p;
  268. $pubKeyPts = AddressCodec::Decompress($pubKey);
  269. // S^-1* hash * G + S^-1 * R * Qa
  270. // S^-1* hash
  271. $exp1 = gmp_strval(
  272. gmp_mul(
  273. gmp_invert(
  274. gmp_init($S, 16),
  275. $n
  276. ),
  277. gmp_init($hash, 16)
  278. ),
  279. 16
  280. );
  281. // S^-1* hash * G
  282. $exp1Pt = PointMathGMP::mulPoint($exp1, $G, $a, $b, $p);
  283. // S^-1 * R
  284. $exp2 = gmp_strval(
  285. gmp_mul(
  286. gmp_invert(
  287. gmp_init($S, 16),
  288. $n
  289. ),
  290. gmp_init($R, 16)
  291. ),
  292. 16
  293. );
  294. // S^-1 * R * Qa
  295. $pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16);
  296. $pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16);
  297. $exp2Pt = PointMathGMP::mulPoint($exp2, $pubKeyPts, $a, $b, $p);
  298. $resultingPt = PointMathGMP::addPoints($exp1Pt, $exp2Pt, $a, $p);
  299. $xRes = gmp_strval($resultingPt['x'], 16);
  300. while(strlen($xRes) < 64)
  301. $xRes = '0' . $xRes;
  302. if($xRes == $R)
  303. return true;
  304. else
  305. return false;
  306. }
  307. /***
  308. * checkSignaturePoints wrapper for DER signatures
  309. *
  310. * @param $pubKey
  311. * @param $signature
  312. * @param $hash
  313. * @return bool
  314. */
  315. public static function checkDerSignature($pubKey, $signature, $hash)
  316. {
  317. $signature = hex2bin($signature);
  318. if('30' != bin2hex(substr($signature, 0, 1)))
  319. return false;
  320. $RLength = hexdec(bin2hex(substr($signature, 3, 1)));
  321. $R = bin2hex(substr($signature, 4, $RLength));
  322. $SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1)));
  323. $S = bin2hex(substr($signature, $RLength + 6, $SLength));
  324. //echo "\n\nsignature:\n";
  325. //print_r(bin2hex($signature));
  326. //echo "\n\nR:\n";
  327. //print_r($R);
  328. //echo "\n\nS:\n";
  329. //print_r($S);
  330. return self::checkSignaturePoints($pubKey, $R, $S, $hash);
  331. }
  332. }
  333. ?>