123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275 |
- <?php
- namespace Elliptic;
- use Elliptic\Curve\PresetCurve;
- use Elliptic\EC\KeyPair;
- use Elliptic\EC\Signature;
- use BN\BN;
- class EC
- {
- public $curve;
- public $n;
- public $nh;
- public $g;
- public $hash;
- function __construct($options)
- {
- if( is_string($options) )
- {
- $options = Curves::getCurve($options);
- }
- if( $options instanceof PresetCurve )
- $options = array("curve" => $options);
- $this->curve = $options["curve"]->curve;
- $this->n = $this->curve->n;
- $this->nh = $this->n->ushrn(1);
- //Point on curve
- $this->g = $options["curve"]->g;
- $this->g->precompute($options["curve"]->n->bitLength() + 1);
- //Hash for function for DRBG
- if( isset($options["hash"]) )
- $this->hash = $options["hash"];
- else
- $this->hash = $options["curve"]->hash;
- }
- public function keyPair($options) {
- return new KeyPair($this, $options);
- }
- public function keyFromPrivate($priv, $enc = false) {
- return KeyPair::fromPrivate($this, $priv, $enc);
- }
- public function keyFromPublic($pub, $enc = false) {
- return KeyPair::fromPublic($this, $pub, $enc);
- }
- public function genKeyPair($options = null)
- {
- // Instantiate HmacDRBG
- $drbg = new HmacDRBG(array(
- "hash" => $this->hash,
- "pers" => isset($options["pers"]) ? $options["pers"] : "",
- "entropy" => isset($options["entropy"]) ? $options["entropy"] : Utils::randBytes($this->hash["hmacStrength"]),
- "nonce" => $this->n->toArray()
- ));
- $bytes = $this->n->byteLength();
- $ns2 = $this->n->sub(new BN(2));
- while(true)
- {
- $priv = new BN($drbg->generate($bytes));
- if( $priv->cmp($ns2) > 0 )
- continue;
- $priv->iaddn(1);
- return $this->keyFromPrivate($priv);
- }
- }
- private function _truncateToN($msg, $truncOnly = false)
- {
- $delta = intval(($msg->byteLength() * 8) - $this->n->bitLength());
- if( $delta > 0 ) {
- $msg = $msg->ushrn($delta);
- }
- if( $truncOnly || $msg->cmp($this->n) < 0 )
- return $msg;
- return $msg->sub($this->n);
- }
- public function sign($msg, $key, $enc = null, $options = null)
- {
- if( !is_string($enc) )
- {
- $options = $enc;
- $enc = null;
- }
- $key = $this->keyFromPrivate($key, $enc);
- $msg = $this->_truncateToN(new BN($msg, 16));
- // Zero-extend key to provide enough entropy
- $bytes = $this->n->byteLength();
- $bkey = $key->getPrivate()->toArray("be", $bytes);
- // Zero-extend nonce to have the same byte size as N
- $nonce = $msg->toArray("be", $bytes);
- $kFunc = null;
- if( isset($options["k"]) )
- $kFunc = $options["k"];
- else
- {
- // Instatiate HmacDRBG
- $drbg = new HmacDRBG(array(
- "hash" => $this->hash,
- "entropy" => $bkey,
- "nonce" => $nonce,
- "pers" => isset($options["pers"]) ? $options["pers"] : "",
- "persEnc" => isset($options["persEnc"]) ? $options["persEnc"] : false
- ));
- $kFunc = function($iter) use ($drbg, $bytes) {
- return new BN($drbg->generate($bytes));
- };
- }
- // Number of bytes to generate
- $ns1 = $this->n->sub(new BN(1));
- $canonical = isset($options["canonical"]) ? $options["canonical"] : false;
- for($iter = 0; true; $iter++)
- {
- $k = $kFunc($iter);
- $k = $this->_truncateToN($k, true);
- if( $k->cmpn(1) <= 0 || $k->cmp($ns1) >= 0 )
- continue;
- // Fix the bit-length of the random nonce,
- // so that it doesn't leak via timing.
- // This does not change that ks = k mod k
- $ks = $k->add($this->n);
- $kt = $ks->add($this->n);
- if ($ks->bitLength() === $this->n->bitLength()) {
- $kp = $this->g->mul($kt);
- } else {
- $kp = $this->g->mul($ks);
- }
- if( $kp->isInfinity() )
- continue;
- $kpX = $kp->getX();
- $r = $kpX->umod($this->n);
- if( $r->isZero() )
- continue;
- $s = $k->invm($this->n)->mul($r->mul($key->getPrivate())->iadd($msg));
- $s = $s->umod($this->n);
- if( $s->isZero() )
- continue;
- $recoveryParam = ($kp->getY()->isOdd() ? 1 : 0) | ($kpX->cmp($r) !== 0 ? 2 : 0);
- // Use complement of `s`, if it is > `n / 2`
- if( $canonical && $s->cmp($this->nh) > 0 )
- {
- $s = $this->n->sub($s);
- $recoveryParam ^= 1;
- }
- return new Signature(array(
- "r" => $r,
- "s" => $s,
- "recoveryParam" => $recoveryParam
- ));
- }
- }
- public function verify($msg, $signature, $key, $enc = false)
- {
- $msg = $this->_truncateToN(new BN($msg, 16));
- $key = $this->keyFromPublic($key, $enc);
- $signature = new Signature($signature, "hex");
- // Perform primitive values validation
- $r = $signature->r;
- $s = $signature->s;
- if( $r->cmpn(1) < 0 || $r->cmp($this->n) >= 0 )
- return false;
- if( $s->cmpn(1) < 0 || $s->cmp($this->n) >= 0 )
- return false;
- // Validate signature
- $sinv = $s->invm($this->n);
- $u1 = $sinv->mul($msg)->umod($this->n);
- $u2 = $sinv->mul($r)->umod($this->n);
- if( !$this->curve->_maxwellTrick )
- {
- $p = $this->g->mulAdd($u1, $key->getPublic(), $u2);
- if( $p->isInfinity() )
- return false;
- return $p->getX()->umod($this->n)->cmp($r) === 0;
- }
- // NOTE: Greg Maxwell's trick, inspired by:
- // https://git.io/vad3K
- $p = $this->g->jmulAdd($u1, $key->getPublic(), $u2);
- if( $p->isInfinity() )
- return false;
- // Compare `p.x` of Jacobian point with `r`,
- // this will do `p.x == r * p.z^2` instead of multiplying `p.x` by the
- // inverse of `p.z^2`
- return $p->eqXToP($r);
- }
- public function recoverPubKey($msg, $signature, $j, $enc = false)
- {
- assert((3 & $j) === $j); //, "The recovery param is more than two bits");
- $signature = new Signature($signature, $enc);
- $e = new BN($msg, 16);
- $r = $signature->r;
- $s = $signature->s;
- // A set LSB signifies that the y-coordinate is odd
- $isYOdd = ($j & 1) == 1;
- $isSecondKey = $j >> 1;
- if ($r->cmp($this->curve->p->umod($this->curve->n)) >= 0 && $isSecondKey)
- throw new \Exception("Unable to find second key candinate");
- // 1.1. Let x = r + jn.
- if( $isSecondKey )
- $r = $this->curve->pointFromX($r->add($this->curve->n), $isYOdd);
- else
- $r = $this->curve->pointFromX($r, $isYOdd);
- $eNeg = $this->n->sub($e);
- // 1.6.1 Compute Q = r^-1 (sR - eG)
- // Q = r^-1 (sR + -eG)
- $rInv = $signature->r->invm($this->n);
- return $this->g->mulAdd($eNeg, $r, $s)->mul($rInv);
- }
- public function getKeyRecoveryParam($e, $signature, $Q, $enc = false)
- {
- $signature = new Signature($signature, $enc);
- if( $signature->recoveryParam != null )
- return $signature->recoveryParam;
- for($i = 0; $i < 4; $i++)
- {
- $Qprime = null;
- try {
- $Qprime = $this->recoverPubKey($e, $signature, $i);
- }
- catch(\Exception $e) {
- continue;
- }
- if( $Qprime->eq($Q))
- return $i;
- }
- throw new \Exception("Unable to find valid recovery factor");
- }
- }
- ?>
|