<?php
namespace Mdanter\Ecc\Tests\Math;
use Mdanter\Ecc\EccFactory;
use Mdanter\Ecc\Math\GmpMathInterface;
use Mdanter\Ecc\Math\NumberTheory;
use Mdanter\Ecc\Tests\AbstractTestCase;
class NumberTheoryTest extends AbstractTestCase
{
/**
* @var
*/
protected $compression_data;
/**
* @var
*/
protected $sqrt_data;
/**
* @var \Mdanter\Ecc\Primitives\GeneratorPoint
*/
protected $generator;
protected function setUp()
{
// file containing a json array of {compressed=>'', decompressed=>''} values
// of compressed and uncompressed ECDSA public keys (testing secp256k1 curve)
$file_comp = TEST_DATA_DIR.'/compression.json';
if (! file_exists($file_comp)) {
$this->fail('Key compression input data not found');
}
$file_sqrt = TEST_DATA_DIR.'/square_root_mod_p.json';
if (! file_exists($file_sqrt)) {
$this->fail('Square root input data not found');
}
$this->generator = EccFactory::getSecgCurves()->generator256k1();
$this->compression_data = json_decode(file_get_contents($file_comp));
$this->sqrt_data = json_decode(file_get_contents($file_sqrt));
}
/**
* @dataProvider getAdapters
* @expectedException \LogicException
*/
public function testSqrtDataWithNoRoots(GmpMathInterface $adapter)
{
$theory = $adapter->getNumberTheory();
foreach ($this->sqrt_data->no_root as $r) {
// todo: turn into adapter
$a = gmp_init($r->a, 10);
$p = gmp_init($r->p, 10);
$theory->squareRootModP($a, $p);
}
}
/**
* @dataProvider getAdapters
*/
public function testSqrtDataWithRoots(GmpMathInterface $adapter)
{
$theory = $adapter->getNumberTheory();
foreach ($this->sqrt_data->has_root as $r) {
$a = gmp_init($r->a, 10);
$p = gmp_init($r->p, 10);
$root1 = $theory->squareRootModP($a, $p);
$root2 = $adapter->sub($p, $root1);
$this->assertTrue(in_array(gmp_strval($root1, 10), $r->res));
$this->assertTrue(in_array(gmp_strval($root2, 10), $r->res));
}
}
/**
* @dataProvider getAdapters
*/
public function testCompressionConsistency(GmpMathInterface $adapter)
{
$theory = $adapter->getNumberTheory();
$this->_doCompressionConsistence($adapter, $theory);
}
public function _doCompressionConsistence(GmpMathInterface $adapter, NumberTheory $theory)
{
foreach ($this->compression_data as $o) {
// Try and regenerate the y coordinate from the parity byte
// '04' . $x_coordinate . determined y coordinate should equal $o->decompressed
// Tests squareRootModP which touches most functions in NumberTheory
$y_byte = substr($o->compressed, 0, 2);
$x_coordinate = substr($o->compressed, 2);
$x = gmp_init($x_coordinate, 16);
// x^3
$x3 = $adapter->powmod($x, gmp_init(3, 10), $this->generator->getCurve()->getPrime());
// y^2
$y2 = $adapter->add(
$x3,
$this->generator->getCurve()->getB()
);
// y0 = sqrt(y^2)
$y0 = $theory->squareRootModP(
$y2,
$this->generator->getCurve()->getPrime()
);
if ($y_byte == '02') {
$y_coordinate = ($adapter->equals($adapter->mod($y0, gmp_init(2, 10)), gmp_init('0')))
? gmp_strval($y0, 16)
: gmp_strval(gmp_sub($this->generator->getCurve()->getPrime(), $y0), 16);
} else {
$y_coordinate = ($adapter->equals($adapter->mod($y0, gmp_init(2, 10)), gmp_init('0')))
? gmp_strval(gmp_sub($this->generator->getCurve()->getPrime(), $y0), 16)
: gmp_strval($y0, 16);
}
$y_coordinate = str_pad($y_coordinate, 64, '0', STR_PAD_LEFT);
// Successfully regenerated uncompressed ECDSA key from the x coordinate and the parity byte.
$this->assertTrue('04'.$x_coordinate.$y_coordinate == $o->decompressed);
}
}
/**
* @dataProvider getAdapters
*/
public function testModFunction(GmpMathInterface $math)
{
// $o->compressed, $o->decompressed public key.
// Check that we can compress a key properly (tests $math->mod())
foreach ($this->compression_data as $o) {
$prefix = substr($o->decompressed, 0, 2); // will be 04.
$this->assertEquals('04', $prefix);
// hex encoded (X,Y) coordinate of ECDSA public key.
$x = substr($o->decompressed, 2, 64);
$y = substr($o->decompressed, 66, 64);
// y % 2 == 0 - true: y is even(02) / false: y is odd(03)
$mod = $math->mod(gmp_init($y, 16), gmp_init(2, 10));
$compressed = '0'.(($math->equals($mod, gmp_init(0))) ? '2' : '3').$x;
// Check that the mod function reported the parity for the y value.
$this->assertTrue($compressed === $o->compressed);
}
}
}