EC.php 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278
  1. <?php
  2. namespace Elliptic;
  3. use Elliptic\Curve\PresetCurve;
  4. use Elliptic\EC\KeyPair;
  5. use Elliptic\EC\Signature;
  6. use BN\BN;
  7. class EC
  8. {
  9. public $curve;
  10. public $n;
  11. public $nh;
  12. public $g;
  13. public $hash;
  14. function __construct($options)
  15. {
  16. if( is_string($options) )
  17. {
  18. $options = Curves::getCurve($options);
  19. }
  20. if( $options instanceof PresetCurve )
  21. $options = array("curve" => $options);
  22. $this->curve = $options["curve"]->curve;
  23. $this->n = $this->curve->n;
  24. $this->nh = $this->n->ushrn(1);
  25. //Point on curve
  26. $this->g = $options["curve"]->g;
  27. $this->g->precompute($options["curve"]->n->bitLength() + 1);
  28. //Hash for function for DRBG
  29. if( isset($options["hash"]) )
  30. $this->hash = $options["hash"];
  31. else
  32. $this->hash = $options["curve"]->hash;
  33. }
  34. public function keyPair(#[\SensitiveParameter]
  35. $options) {
  36. return new KeyPair($this, $options);
  37. }
  38. public function keyFromPrivate(#[\SensitiveParameter]
  39. $priv, $enc = false) {
  40. return KeyPair::fromPrivate($this, $priv, $enc);
  41. }
  42. public function keyFromPublic($pub, $enc = false) {
  43. return KeyPair::fromPublic($this, $pub, $enc);
  44. }
  45. public function genKeyPair($options = null)
  46. {
  47. // Instantiate HmacDRBG
  48. $drbg = new HmacDRBG(array(
  49. "hash" => $this->hash,
  50. "pers" => isset($options["pers"]) ? $options["pers"] : "",
  51. "entropy" => isset($options["entropy"]) ? $options["entropy"] : Utils::randBytes($this->hash["hmacStrength"]),
  52. "nonce" => $this->n->toArray()
  53. ));
  54. $bytes = $this->n->byteLength();
  55. $ns2 = $this->n->sub(new BN(2));
  56. while(true)
  57. {
  58. $priv = new BN($drbg->generate($bytes));
  59. if( $priv->cmp($ns2) > 0 )
  60. continue;
  61. $priv->iaddn(1);
  62. return $this->keyFromPrivate($priv);
  63. }
  64. }
  65. private function _truncateToN($msg, $truncOnly = false)
  66. {
  67. $delta = intval(($msg->byteLength() * 8) - $this->n->bitLength());
  68. if( $delta > 0 ) {
  69. $msg = $msg->ushrn($delta);
  70. }
  71. if( $truncOnly || $msg->cmp($this->n) < 0 )
  72. return $msg;
  73. return $msg->sub($this->n);
  74. }
  75. public function sign($msg, #[\SensitiveParameter]
  76. $key, $enc = null, $options = null)
  77. {
  78. if( !is_string($enc) )
  79. {
  80. $options = $enc;
  81. $enc = null;
  82. }
  83. $key = $this->keyFromPrivate($key, $enc);
  84. $msg = $this->_truncateToN(new BN($msg, 16));
  85. // Zero-extend key to provide enough entropy
  86. $bytes = $this->n->byteLength();
  87. $bkey = $key->getPrivate()->toArray("be", $bytes);
  88. // Zero-extend nonce to have the same byte size as N
  89. $nonce = $msg->toArray("be", $bytes);
  90. $kFunc = null;
  91. if( isset($options["k"]) )
  92. $kFunc = $options["k"];
  93. else
  94. {
  95. // Instatiate HmacDRBG
  96. $drbg = new HmacDRBG(array(
  97. "hash" => $this->hash,
  98. "entropy" => $bkey,
  99. "nonce" => $nonce,
  100. "pers" => isset($options["pers"]) ? $options["pers"] : "",
  101. "persEnc" => isset($options["persEnc"]) ? $options["persEnc"] : false
  102. ));
  103. $kFunc = function($iter) use ($drbg, $bytes) {
  104. return new BN($drbg->generate($bytes));
  105. };
  106. }
  107. // Number of bytes to generate
  108. $ns1 = $this->n->sub(new BN(1));
  109. $canonical = isset($options["canonical"]) ? $options["canonical"] : false;
  110. for($iter = 0; true; $iter++)
  111. {
  112. $k = $kFunc($iter);
  113. $k = $this->_truncateToN($k, true);
  114. if( $k->cmpn(1) <= 0 || $k->cmp($ns1) >= 0 )
  115. continue;
  116. // Fix the bit-length of the random nonce,
  117. // so that it doesn't leak via timing.
  118. // This does not change that ks = k mod k
  119. $ks = $k->add($this->n);
  120. $kt = $ks->add($this->n);
  121. if ($ks->bitLength() === $this->n->bitLength()) {
  122. $kp = $this->g->mul($kt);
  123. } else {
  124. $kp = $this->g->mul($ks);
  125. }
  126. if( $kp->isInfinity() )
  127. continue;
  128. $kpX = $kp->getX();
  129. $r = $kpX->umod($this->n);
  130. if( $r->isZero() )
  131. continue;
  132. $s = $k->invm($this->n)->mul($r->mul($key->getPrivate())->iadd($msg));
  133. $s = $s->umod($this->n);
  134. if( $s->isZero() )
  135. continue;
  136. $recoveryParam = ($kp->getY()->isOdd() ? 1 : 0) | ($kpX->cmp($r) !== 0 ? 2 : 0);
  137. // Use complement of `s`, if it is > `n / 2`
  138. if( $canonical && $s->cmp($this->nh) > 0 )
  139. {
  140. $s = $this->n->sub($s);
  141. $recoveryParam ^= 1;
  142. }
  143. return new Signature(array(
  144. "r" => $r,
  145. "s" => $s,
  146. "recoveryParam" => $recoveryParam
  147. ));
  148. }
  149. }
  150. public function verify($msg, $signature, $key, $enc = false)
  151. {
  152. $msg = $this->_truncateToN(new BN($msg, 16));
  153. $key = $this->keyFromPublic($key, $enc);
  154. $signature = new Signature($signature, "hex");
  155. // Perform primitive values validation
  156. $r = $signature->r;
  157. $s = $signature->s;
  158. if( $r->cmpn(1) < 0 || $r->cmp($this->n) >= 0 )
  159. return false;
  160. if( $s->cmpn(1) < 0 || $s->cmp($this->n) >= 0 )
  161. return false;
  162. // Validate signature
  163. $sinv = $s->invm($this->n);
  164. $u1 = $sinv->mul($msg)->umod($this->n);
  165. $u2 = $sinv->mul($r)->umod($this->n);
  166. if( !$this->curve->_maxwellTrick )
  167. {
  168. $p = $this->g->mulAdd($u1, $key->getPublic(), $u2);
  169. if( $p->isInfinity() )
  170. return false;
  171. return $p->getX()->umod($this->n)->cmp($r) === 0;
  172. }
  173. // NOTE: Greg Maxwell's trick, inspired by:
  174. // https://git.io/vad3K
  175. $p = $this->g->jmulAdd($u1, $key->getPublic(), $u2);
  176. if( $p->isInfinity() )
  177. return false;
  178. // Compare `p.x` of Jacobian point with `r`,
  179. // this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the
  180. // inverse of `p.z^2`
  181. return $p->eqXToP($r);
  182. }
  183. public function recoverPubKey($msg, $signature, $j, $enc = false)
  184. {
  185. assert((3 & $j) === $j); //, "The recovery param is more than two bits");
  186. $signature = new Signature($signature, $enc);
  187. $e = new BN($msg, 16);
  188. $r = $signature->r;
  189. $s = $signature->s;
  190. // A set LSB signifies that the y-coordinate is odd
  191. $isYOdd = ($j & 1) == 1;
  192. $isSecondKey = $j >> 1;
  193. if ($r->cmp($this->curve->p->umod($this->curve->n)) >= 0 && $isSecondKey)
  194. throw new \Exception("Unable to find second key candinate");
  195. // 1.1. Let x = r + jn.
  196. if( $isSecondKey )
  197. $r = $this->curve->pointFromX($r->add($this->curve->n), $isYOdd);
  198. else
  199. $r = $this->curve->pointFromX($r, $isYOdd);
  200. $eNeg = $this->n->sub($e);
  201. // 1.6.1 Compute Q = r^-1 (sR - eG)
  202. // Q = r^-1 (sR + -eG)
  203. $rInv = $signature->r->invm($this->n);
  204. return $this->g->mulAdd($eNeg, $r, $s)->mul($rInv);
  205. }
  206. public function getKeyRecoveryParam($e, $signature, $Q, $enc = false)
  207. {
  208. $signature = new Signature($signature, $enc);
  209. if( $signature->recoveryParam != null )
  210. return $signature->recoveryParam;
  211. for($i = 0; $i < 4; $i++)
  212. {
  213. $Qprime = null;
  214. try {
  215. $Qprime = $this->recoverPubKey($e, $signature, $i);
  216. }
  217. catch(\Exception $e) {
  218. continue;
  219. }
  220. if( $Qprime->eq($Q))
  221. return $i;
  222. }
  223. throw new \Exception("Unable to find valid recovery factor");
  224. }
  225. }
  226. ?>