322 lines
12 KiB
PHP
322 lines
12 KiB
PHP
<?php
|
|
require_once __DIR__ . "/../vendor/autoload.php";
|
|
use BN\BN;
|
|
|
|
class ECDSATest extends \PHPUnit\Framework\TestCase {
|
|
|
|
function ECDSACurveNames() {
|
|
return [
|
|
['secp256k1']
|
|
, ['ed25519']
|
|
, ['p256']
|
|
, ['p384']
|
|
, ['p521']
|
|
];
|
|
}
|
|
|
|
static $entropy = [
|
|
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
|
|
21, 22, 23, 24, 25
|
|
];
|
|
|
|
static $msg = 'deadbeef';
|
|
|
|
protected $curve;
|
|
protected $ecdsa;
|
|
protected $keys;
|
|
|
|
public function prepare($name) {
|
|
$this->curve = \Elliptic\Curves::getCurve($name);
|
|
$this->assertNotNull($this->curve);
|
|
|
|
$this->ecdsa = new \Elliptic\EC($this->curve);
|
|
$this->keys = $this->ecdsa->genKeyPair([ "entropy" => self::$entropy ]);
|
|
return [$this->curve, $this->ecdsa, $this->keys];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_generate_proper_key_pair($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$keylen = 64;
|
|
if ($name == 'p384') {
|
|
$keylen = 96;
|
|
} else if ($name == 'p521') {
|
|
$keylen = 132;
|
|
}
|
|
// Get keys out of pair
|
|
$this->assertTrue( $keys->getPublic()->x && $keys->getPublic()->y );
|
|
$this->assertTrue( $keys->getPrivate()->byteLength() > 0);
|
|
$this->assertEquals( strlen($keys->getPrivate('hex')), $keylen);
|
|
$this->assertTrue( strlen($keys->getPublic('hex')) > 0);
|
|
$this->assertTrue( strlen($keys->getPrivate('hex')) > 0);
|
|
$this->assertTrue( $keys->validate()["result"], 'key validate' );
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_sign_and_verify($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$signature = $ecdsa->sign(self::$msg, $keys);
|
|
$this->assertTrue($ecdsa->verify(self::$msg, $signature, $keys), 'Normal verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_sign_and_verify_using_keys_methods($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$signature = $keys->sign(self::$msg);
|
|
$this->assertTrue($keys->verify(self::$msg, $signature), 'On-key verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_load_private_key_from_the_hex_value($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$copy = $ecdsa->keyFromPrivate($keys->getPrivate('hex'), 'hex');
|
|
$signature = $ecdsa->sign(self::$msg, $copy);
|
|
$this->assertTrue($ecdsa->verify(self::$msg, $signature, $copy), 'hex-private verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_have_signature_s_leq_keys_ec_nh($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
// key.sign(msg, options)
|
|
$sign = $keys->sign('deadbeef', [ "canonical" => true ]);
|
|
$this->assertTrue($sign->s->cmp($keys->ec->nh) <= 0);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_support_options_k($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$sign = $keys->sign(self::$msg, [
|
|
"k" => function($iter) {
|
|
$this->assertTrue($iter >= 0);
|
|
return new BN(1358);
|
|
}
|
|
]);
|
|
$this->assertTrue($ecdsa->verify(self::$msg, $sign, $keys), 'custom-k verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_have_another_signature_with_pers($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$sign1 = $keys->sign(self::$msg);
|
|
$sign2 = $keys->sign(self::$msg, [ "pers" => '1234', "persEnc" => 'hex' ]);
|
|
$this->assertNotEquals($sign1->r->toString('hex') . $sign1->s->toString('hex'),
|
|
$sign2->r->toString('hex') . $sign2->s->toString('hex'));
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_load_public_key_from_compact_hex_value($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$pub = $keys->getPublic(true, 'hex');
|
|
$copy = $ecdsa->keyFromPublic($pub, 'hex');
|
|
$this->assertEquals($copy->getPublic(true, 'hex'), $pub);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_load_public_key_from_hex_value($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$pub = $keys->getPublic('hex');
|
|
$copy = $ecdsa->keyFromPublic($pub, 'hex');
|
|
$this->assertEquals($copy->getPublic('hex'), $pub);
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_support_hex_DER_encoding_of_signatures($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$signature = $ecdsa->sign(self::$msg, $keys);
|
|
$dsign = $signature->toDER('hex');
|
|
$this->assertTrue($ecdsa->verify(self::$msg, $dsign, $keys), 'hex-DER encoded verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_support_DER_encoding_of_signatures($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$signature = $ecdsa->sign(self::$msg, $keys);
|
|
$dsign = $signature->toDER();
|
|
$this->assertTrue($ecdsa->verify(self::$msg, $dsign, $keys), 'DER encoded verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_not_verify_signature_with_wrong_public_key($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$signature = $ecdsa->sign(self::$msg, $keys);
|
|
|
|
$wrong = $ecdsa->genKeyPair();
|
|
$this->assertNotTrue($ecdsa->verify(self::$msg, $signature, $wrong), 'Wrong key verify');
|
|
}
|
|
|
|
/**
|
|
* @dataProvider ECDSACurveNames
|
|
*/
|
|
public function test_should_not_verify_signature_with_wrong_private_key($name) {
|
|
list($curve, $ecdsa, $keys) = $this->prepare($name);
|
|
$signature = $ecdsa->sign(self::$msg, $keys);
|
|
|
|
$wrong = $ecdsa->keyFromPrivate($keys->getPrivate('hex') .
|
|
$keys->getPrivate('hex'), 'hex');
|
|
$this->assertNotTrue($ecdsa->verify(self::$msg, $signature, $wrong), 'Wrong key verify');
|
|
}
|
|
|
|
|
|
// TODO: Implement RFC6979 vectors test
|
|
|
|
|
|
function MaxwellsTrickVector() {
|
|
$p256 = \Elliptic\Curves::getCurve("p256");
|
|
$p384 = \Elliptic\Curves::getCurve("p384");
|
|
$msg = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
|
|
return [
|
|
[[
|
|
"curve" => $p256,
|
|
"pub" => '041548fc88953e06cd34d4b300804c5322cb48c24aaaa4d0' .
|
|
'7a541b0f0ccfeedeb0ae4991b90519ea405588bdf699f5e6' .
|
|
'd0c6b2d5217a5c16e8371062737aa1dae1',
|
|
"message" => $msg,
|
|
"sig" => '3006020106020104',
|
|
"result" => true
|
|
]],
|
|
[[
|
|
"curve" => $p256,
|
|
"pub" => '04ad8f60e4ec1ebdb6a260b559cb55b1e9d2c5ddd43a41a2' .
|
|
'd11b0741ef2567d84e166737664104ebbc337af3d861d352' .
|
|
'4cfbc761c12edae974a0759750c8324f9a',
|
|
"message" => $msg,
|
|
"sig" => '3006020106020104',
|
|
"result" => true
|
|
]],
|
|
[[
|
|
"curve" => $p256,
|
|
"pub" => '0445bd879143a64af5746e2e82aa65fd2ea07bba4e355940' .
|
|
'95a981b59984dacb219d59697387ac721b1f1eccf4b11f43' .
|
|
'ddc39e8367147abab3084142ed3ea170e4',
|
|
"message" => $msg,
|
|
"sig" => '301502104319055358e8617b0c46353d039cdaae020104',
|
|
"result" => true
|
|
]],
|
|
[[
|
|
"curve" => $p256,
|
|
"pub" => '040feb5df4cc78b35ec9c180cc0de5842f75f088b4845697' .
|
|
'8ffa98e716d94883e1e6500b2a1f6c1d9d493428d7ae7d9a' .
|
|
'8a560fff30a3d14aa160be0c5e7edcd887',
|
|
"message" => $msg,
|
|
"sig" => '301502104319055358e8617b0c46353d039cdaae020104',
|
|
"result" => false
|
|
]],
|
|
[[
|
|
"curve" => $p384,
|
|
"pub" => '0425e299eea9927b39fa92417705391bf17e8110b4615e9e' .
|
|
'b5da471b57be0c30e7d89dbdc3e5da4eae029b300344d385' .
|
|
'1548b59ed8be668813905105e673319d59d32f574e180568' .
|
|
'463c6186864888f6c0b67b304441f82aab031279e48f047c31',
|
|
"message" => $msg,
|
|
"sig" => '3006020103020104',
|
|
"result" => true
|
|
]],
|
|
[[
|
|
"curve" => $p384,
|
|
"pub" => '04a328f65c22307188b4af65779c1d2ec821c6748c6bd8dc' .
|
|
'0e6a008135f048f832df501f7f3f79966b03d5bef2f187ec' .
|
|
'34d85f6a934af465656fb4eea8dd9176ab80fbb4a27a649f' .
|
|
'526a7dfe616091b78d293552bc093dfde9b31cae69d51d3afb',
|
|
"message" => $msg,
|
|
"sig" => '3006020103020104',
|
|
"result" => true
|
|
]],
|
|
[[
|
|
"curve" => $p384,
|
|
"pub" => '04242e8585eaa7a28cc6062cab4c9c5fd536f46b17be1728' .
|
|
'288a2cda5951df4941aed1d712defda023d10aca1c5ee014' .
|
|
'43e8beacd821f7efa27847418ab95ce2c514b2b6b395ee73' .
|
|
'417c83dbcad631421f360d84d64658c98a62d685b220f5aad4',
|
|
"message" => $msg,
|
|
"sig" => '301d0218389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68e020104',
|
|
"result" => true
|
|
]],
|
|
[[
|
|
"curve" => $p384,
|
|
"pub" => '04cdf865dd743fe1c23757ec5e65fd5e4038b472ded2af26' .
|
|
'1e3d8343c595c8b69147df46379c7ca40e60e80170d34a11' .
|
|
'88dbb2b6f7d3934c23d2f78cfb0db3f3219959fad63c9b61' .
|
|
'2ef2f20d679777b84192ce86e781c14b1bbb77eacd6e0520e2',
|
|
"message" => $msg,
|
|
"sig" => '301d0218389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68e020104',
|
|
"result" => false
|
|
]]
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @dataProvider MaxwellsTrickVector
|
|
*/
|
|
public function test_should_pass_on_Maxwells_trick_vectors($vector) {
|
|
$ecdsa = new \Elliptic\EC($vector["curve"]);
|
|
$key = $ecdsa->keyFromPublic($vector["pub"], 'hex');
|
|
$msg = $vector["message"];
|
|
$sig = $vector["sig"];
|
|
|
|
$actual = $ecdsa->verify($msg, $sig, $key);
|
|
$this->assertEquals($actual, $vector["result"]);
|
|
}
|
|
|
|
|
|
|
|
public function test_should_deterministically_generate_private_key() {
|
|
$curve = \Elliptic\Curves::getCurve("secp256k1");
|
|
$this->assertNotNull($curve);
|
|
|
|
$ecdsa = new \Elliptic\EC($curve);
|
|
$keys = $ecdsa->genKeyPair(array(
|
|
"pers" => 'my.pers.string',
|
|
"entropy" => hash('sha256', 'hello world', true)
|
|
));
|
|
$this->assertEquals(
|
|
$keys->getPrivate('hex'),
|
|
'6160edb2b218b7f1394b9ca8eb65a72831032a1f2f3dc2d99291c2f7950ed887');
|
|
}
|
|
|
|
public function test_should_recover_the_public_key_from_a_signature() {
|
|
$ec = new \Elliptic\EC('secp256k1');
|
|
$key = $ec->genKeyPair();
|
|
$msg = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
|
|
$signature = $key->sign($msg);
|
|
$recid = $ec->getKeyRecoveryParam($msg, $signature, $key->getPublic());
|
|
$r = $ec->recoverPubKey($msg, $signature, $recid);
|
|
$this->assertTrue($key->getPublic()->eq($r), 'the keys should match');
|
|
}
|
|
|
|
public function test_should_fail_to_recover_key_when_no_quadratic_residue_available() {
|
|
$ec = new \Elliptic\EC('secp256k1');
|
|
$message =
|
|
'f75c6b18a72fabc0f0b888c3da58e004f0af1fe14f7ca5d8c897fe164925d5e9';
|
|
|
|
$this->expectException(\Exception::class);
|
|
$ec->recoverPubKey($message, [
|
|
"r" => 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
|
|
"s" => '8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3'
|
|
], 0);
|
|
}
|
|
}
|