222 lines
5.6 KiB
PHP
222 lines
5.6 KiB
PHP
<?php
|
|
|
|
namespace BN;
|
|
|
|
use \Exception;
|
|
use \BI\BigInteger;
|
|
|
|
class Red
|
|
{
|
|
public static $ASSERT_ENABLED;
|
|
public $m;
|
|
|
|
function __construct($m) {
|
|
if( is_string($m) )
|
|
$this->m = Red::primeByName($m);
|
|
else
|
|
$this->m = $m;
|
|
|
|
if( !$this->m->gtn(1) )
|
|
throw new Exception("Modulus must be greater than 1");
|
|
}
|
|
|
|
|
|
public static function primeByName($name)
|
|
{
|
|
switch($name) {
|
|
case "k256":
|
|
return new BN("ffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f", 16);
|
|
case "p224":
|
|
return new BN("ffffffff ffffffff ffffffff ffffffff 00000000 00000000 00000001", 16);
|
|
case "p192":
|
|
return new BN("ffffffff ffffffff ffffffff fffffffe ffffffff ffffffff", 16);
|
|
case "p25519":
|
|
return new BN("7fffffffffffffff ffffffffffffffff ffffffffffffffff ffffffffffffffed", 16);
|
|
default:
|
|
throw new Exception("Unknown prime name " . $name);
|
|
}
|
|
}
|
|
|
|
public function verify1(BN $num)
|
|
{
|
|
if (Red::$ASSERT_ENABLED) assert(!$num->negative()); //,"red works only with positives");
|
|
assert($num->red); //, "red works only with red numbers");
|
|
}
|
|
|
|
public function verify2(BN $a, BN $b)
|
|
{
|
|
if (Red::$ASSERT_ENABLED) assert(!$a->negative() && !$b->negative()); //, "red works only with positives");
|
|
assert($a->red && ($a->red == $b->red)); //, "red works only with red numbers");
|
|
}
|
|
|
|
public function imod(BN &$a) {
|
|
return $a->umod($this->m)->_forceRed($this);
|
|
}
|
|
|
|
public function neg(BN $a)
|
|
{
|
|
if( $a->isZero() )
|
|
return $a->_clone();
|
|
return $this->m->sub($a)->_forceRed($this);
|
|
}
|
|
|
|
public function add(BN $a, BN $b)
|
|
{
|
|
$this->verify2($a, $b);
|
|
|
|
$res = $a->add($b);
|
|
if( $res->cmp($this->m) >= 0 )
|
|
$res->isub($this->m);
|
|
return $res->_forceRed($this);
|
|
}
|
|
|
|
public function iadd(BN &$a, BN $b)
|
|
{
|
|
$this->verify2($a, $b);
|
|
|
|
$a->iadd($b);
|
|
if( $a->cmp($this->m) >= 0 )
|
|
$a->isub($this->m);
|
|
|
|
return $a;
|
|
}
|
|
|
|
public function sub(BN $a, BN $b)
|
|
{
|
|
$this->verify2($a, $b);
|
|
|
|
$res = $a->sub($b);
|
|
if( $res->negative() )
|
|
$res->iadd($this->m);
|
|
|
|
return $res->_forceRed($this);
|
|
}
|
|
|
|
public function isub(BN &$a, $b)
|
|
{
|
|
$this->verify2($a, $b);
|
|
|
|
$a->isub($b);
|
|
if( $a->negative() )
|
|
$a->iadd($this->m);
|
|
|
|
return $a;
|
|
}
|
|
|
|
public function shl(BN $a, $num) {
|
|
$this->verify1($a);
|
|
return $this->imod($a->ushln($num));
|
|
}
|
|
|
|
public function imul(BN &$a, BN $b) {
|
|
$this->verify2($a, $b);
|
|
$res = $a->imul($b);
|
|
return $this->imod($res);
|
|
}
|
|
|
|
public function mul(BN $a, BN $b) {
|
|
$this->verify2($a, $b);
|
|
$res = $a->mul($b);
|
|
return $this->imod($res);
|
|
}
|
|
|
|
public function sqr(BN $a) {
|
|
$res = $a->_clone();
|
|
return $this->imul($res, $a);
|
|
}
|
|
|
|
public function isqr(BN &$a) {
|
|
return $this->imul($a, $a);
|
|
}
|
|
|
|
public function sqrt(BN $a) {
|
|
if ($a->isZero())
|
|
return $a->_clone();
|
|
|
|
$mod3 = $this->m->andln(3);
|
|
assert($mod3 % 2 == 1);
|
|
|
|
// Fast case
|
|
if ($mod3 == 3) {
|
|
$pow = $this->m->add(new BN(1))->iushrn(2);
|
|
return $this->pow($a, $pow);
|
|
}
|
|
|
|
// Tonelli-Shanks algorithm (Totally unoptimized and slow)
|
|
//
|
|
// Find Q and S, that Q * 2 ^ S = (P - 1)
|
|
$q = $this->m->subn(1);
|
|
$s = 0;
|
|
while (!$q->isZero() && $q->andln(1) == 0) {
|
|
$s++;
|
|
$q->iushrn(1);
|
|
}
|
|
if (Red::$ASSERT_ENABLED) assert(!$q->isZero());
|
|
|
|
$one = (new BN(1))->toRed($this);
|
|
$nOne = $one->redNeg();
|
|
|
|
// Find quadratic non-residue
|
|
// NOTE: Max is such because of generalized Riemann hypothesis.
|
|
$lpow = $this->m->subn(1)->iushrn(1);
|
|
$z = $this->m->bitLength();
|
|
$z = (new BN(2 * $z * $z))->toRed($this);
|
|
|
|
while ($this->pow($z, $lpow)->cmp($nOne) != 0) {
|
|
$z->redIAdd($nOne);
|
|
}
|
|
|
|
$c = $this->pow($z, $q);
|
|
$r = $this->pow($a, $q->addn(1)->iushrn(1));
|
|
$t = $this->pow($a, $q);
|
|
$m = $s;
|
|
while ($t->cmp($one) != 0) {
|
|
$tmp = $t;
|
|
for ($i = 0; $tmp->cmp($one) != 0; $i++) {
|
|
$tmp = $tmp->redSqr();
|
|
}
|
|
if ($i >= $m) {
|
|
throw new \Exception("Assertion failed");
|
|
}
|
|
if ($m - $i - 1 > 54) {
|
|
$b = $this->pow($c, (new BN(1))->iushln($m - $i - 1));
|
|
} else {
|
|
$b = clone($c);
|
|
$b->bi = $c->bi->powMod(1 << ($m - $i - 1), $this->m->bi);
|
|
}
|
|
|
|
$r = $r->redMul($b);
|
|
$c = $b->redSqr();
|
|
$t = $t->redMul($c);
|
|
$m = $i;
|
|
}
|
|
|
|
return $r;
|
|
}
|
|
|
|
public function invm(BN &$a) {
|
|
$res = $a->invm($this->m);
|
|
return $this->imod($res);
|
|
}
|
|
|
|
public function pow(BN $a, BN $num) {
|
|
$r = clone($a);
|
|
$r->bi = $a->bi->powMod($num->bi, $this->m->bi);
|
|
return $r;
|
|
}
|
|
|
|
public function convertTo(BN $num) {
|
|
$r = $num->umod($this->m);
|
|
return $r === $num ? $r->_clone() : $r;
|
|
}
|
|
|
|
public function convertFrom(BN $num) {
|
|
$res = $num->_clone();
|
|
$res->red = null;
|
|
return $res;
|
|
}
|
|
}
|
|
Red::$ASSERT_ENABLED = ini_get("zend.assertions") === "1";
|
|
|
|
?>
|