size = $size; $this->prime = $prime; $this->a = $a; $this->b = $b; $this->generator = $generator; } public function getA(): \GMP { return $this->a; } public function getB(): \GMP { return $this->b; } public function getPrime(): \GMP { return $this->prime; } public function getSize(): int { return $this->size; } public function getPoint(\GMP $x, \GMP $y, ?\GMP $order = null): Point { if (!$this->contains($x, $y)) { throw new \RuntimeException('Curve '.$this->__toString().' does not contain point ('.Math::toString($x).', '.Math::toString($y).')'); } $point = Point::create($x, $y, $order); if (!\is_null($order)) { $mul = $this->mul($point, $order); if (!$mul->isInfinity()) { throw new \RuntimeException('SELF * ORDER MUST EQUAL INFINITY. ('.(string) $mul.' found instead)'); } } return $point; } public function getPublicKeyFrom(\GMP $x, \GMP $y): PublicKey { $zero = \gmp_init(0, 10); if (Math::cmp($x, $zero) < 0 || Math::cmp($this->generator->getOrder(), $x) <= 0 || Math::cmp($y, $zero) < 0 || Math::cmp($this->generator->getOrder(), $y) <= 0) { throw new \RuntimeException('Generator point has x and y out of range.'); } $point = $this->getPoint($x, $y); return PublicKey::create($point); } public function contains(\GMP $x, \GMP $y): bool { $eq_zero = Math::equals( ModularArithmetic::sub( Math::pow($y, 2), Math::add( Math::add( Math::pow($x, 3), Math::mul($this->getA(), $x) ), $this->getB() ), $this->getPrime() ), \gmp_init(0, 10) ); return $eq_zero; } public function add(Point $one, Point $two): Point { if ($two->isInfinity()) { return clone $one; } if ($one->isInfinity()) { return clone $two; } if (Math::equals($two->getX(), $one->getX())) { if (Math::equals($two->getY(), $one->getY())) { return $this->getDouble($one); } else { return Point::infinity(); } } $slope = ModularArithmetic::div( Math::sub($two->getY(), $one->getY()), Math::sub($two->getX(), $one->getX()), $this->getPrime() ); $xR = ModularArithmetic::sub( Math::sub(Math::pow($slope, 2), $one->getX()), $two->getX(), $this->getPrime() ); $yR = ModularArithmetic::sub( Math::mul($slope, Math::sub($one->getX(), $xR)), $one->getY(), $this->getPrime() ); return $this->getPoint($xR, $yR, $one->getOrder()); } public function mul(Point $one, \GMP $n): Point { if ($one->isInfinity()) { return Point::infinity(); } /** @var \GMP $zero */ $zero = \gmp_init(0, 10); if (Math::cmp($one->getOrder(), $zero) > 0) { $n = Math::mod($n, $one->getOrder()); } if (Math::equals($n, $zero)) { return Point::infinity(); } /** @var Point[] $r */ $r = [ Point::infinity(), clone $one, ]; $k = $this->getSize(); $n = \str_pad(Math::baseConvert(Math::toString($n), 10, 2), $k, '0', STR_PAD_LEFT); for ($i = 0; $i < $k; ++$i) { $j = $n[$i]; Point::cswap($r[0], $r[1], $j ^ 1); $r[0] = $this->add($r[0], $r[1]); $r[1] = $this->getDouble($r[1]); Point::cswap($r[0], $r[1], $j ^ 1); } $this->validate($r[0]); return $r[0]; } /** * @param Curve $other */ public function cmp(self $other): int { $equal = Math::equals($this->getA(), $other->getA()); $equal &= Math::equals($this->getB(), $other->getB()); $equal &= Math::equals($this->getPrime(), $other->getPrime()); return $equal ? 0 : 1; } /** * @param Curve $other */ public function equals(self $other): bool { return 0 === $this->cmp($other); } public function __toString(): string { return 'curve('.Math::toString($this->getA()).', '.Math::toString($this->getB()).', '.Math::toString($this->getPrime()).')'; } private function validate(Point $point) { if (!$point->isInfinity() && !$this->contains($point->getX(), $point->getY())) { throw new \RuntimeException('Invalid point'); } } public function getDouble(Point $point): Point { if ($point->isInfinity()) { return Point::infinity(); } $a = $this->getA(); $threeX2 = Math::mul(\gmp_init(3, 10), Math::pow($point->getX(), 2)); $tangent = ModularArithmetic::div( Math::add($threeX2, $a), Math::mul(\gmp_init(2, 10), $point->getY()), $this->getPrime() ); $x3 = ModularArithmetic::sub( Math::pow($tangent, 2), Math::mul(\gmp_init(2, 10), $point->getX()), $this->getPrime() ); $y3 = ModularArithmetic::sub( Math::mul($tangent, Math::sub($point->getX(), $x3)), $point->getY(), $this->getPrime() ); return $this->getPoint($x3, $y3, $point->getOrder()); } public function createPrivateKey(): PrivateKey { return PrivateKey::create($this->generate()); } public function createPublicKey(PrivateKey $privateKey): PublicKey { $point = $this->mul($this->generator, $privateKey->getSecret()); return PublicKey::create($point); } private function generate(): \GMP { $max = $this->generator->getOrder(); $numBits = $this->bnNumBits($max); $numBytes = (int) \ceil($numBits / 8); // Generate an integer of size >= $numBits $bytes = \random_bytes($numBytes); $value = Math::stringToInt($bytes); $mask = \gmp_sub(\gmp_pow(2, $numBits), 1); $integer = \gmp_and($value, $mask); return $integer; } /** * Returns the number of bits used to store this number. Non-significant upper bits are not counted. * * @see https://www.openssl.org/docs/crypto/BN_num_bytes.html */ private function bnNumBits(\GMP $x): int { $zero = \gmp_init(0, 10); if (Math::equals($x, $zero)) { return 0; } $log2 = 0; while (false === Math::equals($x, $zero)) { $x = Math::rightShift($x, 1); ++$log2; } return $log2; } public function getGenerator(): Point { return $this->generator; } }