size = $size; $this->prime = $prime; $this->a = $a; $this->b = $b; $this->generator = $generator; } public function __toString(): string { return 'curve('.Math::toString($this->getA()).', '.Math::toString($this->getB()).', '.Math::toString($this->getPrime()).')'; } 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; } /** * @throws RuntimeException if the curve does not contain the point */ 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.'); } } return $point; } /** * @throws RuntimeException if the coordinates are out of range */ 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 new PublicKey($point); } public function contains(GMP $x, GMP $y): bool { return 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) ); } 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); } 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()) && Math::equals($this->getB(), $other->getB()) && 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 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 new PublicKey($point); } public function getGenerator(): Point { return $this->generator; } /** * @throws RuntimeException if the point is invalid */ private function validate(Point $point): void { if (!$point->isInfinity() && !$this->contains($point->getX(), $point->getY())) { throw new RuntimeException('Invalid 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); return gmp_and($value, $mask); } /** * 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; } }