ECDSATest.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. <?php
  2. require_once __DIR__ . "/../vendor/autoload.php";
  3. use BN\BN;
  4. class ECDSATest extends \PHPUnit\Framework\TestCase {
  5. function ECDSACurveNames() {
  6. return [
  7. ['secp256k1']
  8. , ['ed25519']
  9. , ['p256']
  10. , ['p384']
  11. , ['p521']
  12. ];
  13. }
  14. static $entropy = [
  15. 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
  16. 21, 22, 23, 24, 25
  17. ];
  18. static $msg = 'deadbeef';
  19. protected $curve;
  20. protected $ecdsa;
  21. protected $keys;
  22. public function prepare($name) {
  23. $this->curve = \Elliptic\Curves::getCurve($name);
  24. $this->assertNotNull($this->curve);
  25. $this->ecdsa = new \Elliptic\EC($this->curve);
  26. $this->keys = $this->ecdsa->genKeyPair([ "entropy" => self::$entropy ]);
  27. return [$this->curve, $this->ecdsa, $this->keys];
  28. }
  29. /**
  30. * @dataProvider ECDSACurveNames
  31. */
  32. public function test_should_generate_proper_key_pair($name) {
  33. list($curve, $ecdsa, $keys) = $this->prepare($name);
  34. $keylen = 64;
  35. if ($name == 'p384') {
  36. $keylen = 96;
  37. } else if ($name == 'p521') {
  38. $keylen = 132;
  39. }
  40. // Get keys out of pair
  41. $this->assertTrue( $keys->getPublic()->x && $keys->getPublic()->y );
  42. $this->assertTrue( $keys->getPrivate()->byteLength() > 0);
  43. $this->assertEquals( strlen($keys->getPrivate('hex')), $keylen);
  44. $this->assertTrue( strlen($keys->getPublic('hex')) > 0);
  45. $this->assertTrue( strlen($keys->getPrivate('hex')) > 0);
  46. $this->assertTrue( $keys->validate()["result"], 'key validate' );
  47. }
  48. /**
  49. * @dataProvider ECDSACurveNames
  50. */
  51. public function test_should_sign_and_verify($name) {
  52. list($curve, $ecdsa, $keys) = $this->prepare($name);
  53. $signature = $ecdsa->sign(self::$msg, $keys);
  54. $this->assertTrue($ecdsa->verify(self::$msg, $signature, $keys), 'Normal verify');
  55. }
  56. /**
  57. * @dataProvider ECDSACurveNames
  58. */
  59. public function test_should_sign_and_verify_using_keys_methods($name) {
  60. list($curve, $ecdsa, $keys) = $this->prepare($name);
  61. $signature = $keys->sign(self::$msg);
  62. $this->assertTrue($keys->verify(self::$msg, $signature), 'On-key verify');
  63. }
  64. /**
  65. * @dataProvider ECDSACurveNames
  66. */
  67. public function test_should_load_private_key_from_the_hex_value($name) {
  68. list($curve, $ecdsa, $keys) = $this->prepare($name);
  69. $copy = $ecdsa->keyFromPrivate($keys->getPrivate('hex'), 'hex');
  70. $signature = $ecdsa->sign(self::$msg, $copy);
  71. $this->assertTrue($ecdsa->verify(self::$msg, $signature, $copy), 'hex-private verify');
  72. }
  73. /**
  74. * @dataProvider ECDSACurveNames
  75. */
  76. public function test_should_have_signature_s_leq_keys_ec_nh($name) {
  77. list($curve, $ecdsa, $keys) = $this->prepare($name);
  78. // key.sign(msg, options)
  79. $sign = $keys->sign('deadbeef', [ "canonical" => true ]);
  80. $this->assertTrue($sign->s->cmp($keys->ec->nh) <= 0);
  81. }
  82. /**
  83. * @dataProvider ECDSACurveNames
  84. */
  85. public function test_should_support_options_k($name) {
  86. list($curve, $ecdsa, $keys) = $this->prepare($name);
  87. $sign = $keys->sign(self::$msg, [
  88. "k" => function($iter) {
  89. $this->assertTrue($iter >= 0);
  90. return new BN(1358);
  91. }
  92. ]);
  93. $this->assertTrue($ecdsa->verify(self::$msg, $sign, $keys), 'custom-k verify');
  94. }
  95. /**
  96. * @dataProvider ECDSACurveNames
  97. */
  98. public function test_should_have_another_signature_with_pers($name) {
  99. list($curve, $ecdsa, $keys) = $this->prepare($name);
  100. $sign1 = $keys->sign(self::$msg);
  101. $sign2 = $keys->sign(self::$msg, [ "pers" => '1234', "persEnc" => 'hex' ]);
  102. $this->assertNotEquals($sign1->r->toString('hex') . $sign1->s->toString('hex'),
  103. $sign2->r->toString('hex') . $sign2->s->toString('hex'));
  104. }
  105. /**
  106. * @dataProvider ECDSACurveNames
  107. */
  108. public function test_should_load_public_key_from_compact_hex_value($name) {
  109. list($curve, $ecdsa, $keys) = $this->prepare($name);
  110. $pub = $keys->getPublic(true, 'hex');
  111. $copy = $ecdsa->keyFromPublic($pub, 'hex');
  112. $this->assertEquals($copy->getPublic(true, 'hex'), $pub);
  113. }
  114. /**
  115. * @dataProvider ECDSACurveNames
  116. */
  117. public function test_should_load_public_key_from_hex_value($name) {
  118. list($curve, $ecdsa, $keys) = $this->prepare($name);
  119. $pub = $keys->getPublic('hex');
  120. $copy = $ecdsa->keyFromPublic($pub, 'hex');
  121. $this->assertEquals($copy->getPublic('hex'), $pub);
  122. }
  123. /**
  124. * @dataProvider ECDSACurveNames
  125. */
  126. public function test_should_support_hex_DER_encoding_of_signatures($name) {
  127. list($curve, $ecdsa, $keys) = $this->prepare($name);
  128. $signature = $ecdsa->sign(self::$msg, $keys);
  129. $dsign = $signature->toDER('hex');
  130. $this->assertTrue($ecdsa->verify(self::$msg, $dsign, $keys), 'hex-DER encoded verify');
  131. }
  132. /**
  133. * @dataProvider ECDSACurveNames
  134. */
  135. public function test_should_support_DER_encoding_of_signatures($name) {
  136. list($curve, $ecdsa, $keys) = $this->prepare($name);
  137. $signature = $ecdsa->sign(self::$msg, $keys);
  138. $dsign = $signature->toDER();
  139. $this->assertTrue($ecdsa->verify(self::$msg, $dsign, $keys), 'DER encoded verify');
  140. }
  141. /**
  142. * @dataProvider ECDSACurveNames
  143. */
  144. public function test_should_not_verify_signature_with_wrong_public_key($name) {
  145. list($curve, $ecdsa, $keys) = $this->prepare($name);
  146. $signature = $ecdsa->sign(self::$msg, $keys);
  147. $wrong = $ecdsa->genKeyPair();
  148. $this->assertNotTrue($ecdsa->verify(self::$msg, $signature, $wrong), 'Wrong key verify');
  149. }
  150. /**
  151. * @dataProvider ECDSACurveNames
  152. */
  153. public function test_should_not_verify_signature_with_wrong_private_key($name) {
  154. list($curve, $ecdsa, $keys) = $this->prepare($name);
  155. $signature = $ecdsa->sign(self::$msg, $keys);
  156. $wrong = $ecdsa->keyFromPrivate($keys->getPrivate('hex') .
  157. $keys->getPrivate('hex'), 'hex');
  158. $this->assertNotTrue($ecdsa->verify(self::$msg, $signature, $wrong), 'Wrong key verify');
  159. }
  160. // TODO: Implement RFC6979 vectors test
  161. function MaxwellsTrickVector() {
  162. $p256 = \Elliptic\Curves::getCurve("p256");
  163. $p384 = \Elliptic\Curves::getCurve("p384");
  164. $msg = 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855';
  165. return [
  166. [[
  167. "curve" => $p256,
  168. "pub" => '041548fc88953e06cd34d4b300804c5322cb48c24aaaa4d0' .
  169. '7a541b0f0ccfeedeb0ae4991b90519ea405588bdf699f5e6' .
  170. 'd0c6b2d5217a5c16e8371062737aa1dae1',
  171. "message" => $msg,
  172. "sig" => '3006020106020104',
  173. "result" => true
  174. ]],
  175. [[
  176. "curve" => $p256,
  177. "pub" => '04ad8f60e4ec1ebdb6a260b559cb55b1e9d2c5ddd43a41a2' .
  178. 'd11b0741ef2567d84e166737664104ebbc337af3d861d352' .
  179. '4cfbc761c12edae974a0759750c8324f9a',
  180. "message" => $msg,
  181. "sig" => '3006020106020104',
  182. "result" => true
  183. ]],
  184. [[
  185. "curve" => $p256,
  186. "pub" => '0445bd879143a64af5746e2e82aa65fd2ea07bba4e355940' .
  187. '95a981b59984dacb219d59697387ac721b1f1eccf4b11f43' .
  188. 'ddc39e8367147abab3084142ed3ea170e4',
  189. "message" => $msg,
  190. "sig" => '301502104319055358e8617b0c46353d039cdaae020104',
  191. "result" => true
  192. ]],
  193. [[
  194. "curve" => $p256,
  195. "pub" => '040feb5df4cc78b35ec9c180cc0de5842f75f088b4845697' .
  196. '8ffa98e716d94883e1e6500b2a1f6c1d9d493428d7ae7d9a' .
  197. '8a560fff30a3d14aa160be0c5e7edcd887',
  198. "message" => $msg,
  199. "sig" => '301502104319055358e8617b0c46353d039cdaae020104',
  200. "result" => false
  201. ]],
  202. [[
  203. "curve" => $p384,
  204. "pub" => '0425e299eea9927b39fa92417705391bf17e8110b4615e9e' .
  205. 'b5da471b57be0c30e7d89dbdc3e5da4eae029b300344d385' .
  206. '1548b59ed8be668813905105e673319d59d32f574e180568' .
  207. '463c6186864888f6c0b67b304441f82aab031279e48f047c31',
  208. "message" => $msg,
  209. "sig" => '3006020103020104',
  210. "result" => true
  211. ]],
  212. [[
  213. "curve" => $p384,
  214. "pub" => '04a328f65c22307188b4af65779c1d2ec821c6748c6bd8dc' .
  215. '0e6a008135f048f832df501f7f3f79966b03d5bef2f187ec' .
  216. '34d85f6a934af465656fb4eea8dd9176ab80fbb4a27a649f' .
  217. '526a7dfe616091b78d293552bc093dfde9b31cae69d51d3afb',
  218. "message" => $msg,
  219. "sig" => '3006020103020104',
  220. "result" => true
  221. ]],
  222. [[
  223. "curve" => $p384,
  224. "pub" => '04242e8585eaa7a28cc6062cab4c9c5fd536f46b17be1728' .
  225. '288a2cda5951df4941aed1d712defda023d10aca1c5ee014' .
  226. '43e8beacd821f7efa27847418ab95ce2c514b2b6b395ee73' .
  227. '417c83dbcad631421f360d84d64658c98a62d685b220f5aad4',
  228. "message" => $msg,
  229. "sig" => '301d0218389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68e020104',
  230. "result" => true
  231. ]],
  232. [[
  233. "curve" => $p384,
  234. "pub" => '04cdf865dd743fe1c23757ec5e65fd5e4038b472ded2af26' .
  235. '1e3d8343c595c8b69147df46379c7ca40e60e80170d34a11' .
  236. '88dbb2b6f7d3934c23d2f78cfb0db3f3219959fad63c9b61' .
  237. '2ef2f20d679777b84192ce86e781c14b1bbb77eacd6e0520e2',
  238. "message" => $msg,
  239. "sig" => '301d0218389cb27e0bc8d21fa7e5f24cb74f58851313e696333ad68e020104',
  240. "result" => false
  241. ]]
  242. ];
  243. }
  244. /**
  245. * @dataProvider MaxwellsTrickVector
  246. */
  247. public function test_should_pass_on_Maxwells_trick_vectors($vector) {
  248. $ecdsa = new \Elliptic\EC($vector["curve"]);
  249. $key = $ecdsa->keyFromPublic($vector["pub"], 'hex');
  250. $msg = $vector["message"];
  251. $sig = $vector["sig"];
  252. $actual = $ecdsa->verify($msg, $sig, $key);
  253. $this->assertEquals($actual, $vector["result"]);
  254. }
  255. public function test_should_deterministically_generate_private_key() {
  256. $curve = \Elliptic\Curves::getCurve("secp256k1");
  257. $this->assertNotNull($curve);
  258. $ecdsa = new \Elliptic\EC($curve);
  259. $keys = $ecdsa->genKeyPair(array(
  260. "pers" => 'my.pers.string',
  261. "entropy" => hash('sha256', 'hello world', true)
  262. ));
  263. $this->assertEquals(
  264. $keys->getPrivate('hex'),
  265. '6160edb2b218b7f1394b9ca8eb65a72831032a1f2f3dc2d99291c2f7950ed887');
  266. }
  267. public function test_should_recover_the_public_key_from_a_signature() {
  268. $ec = new \Elliptic\EC('secp256k1');
  269. $key = $ec->genKeyPair();
  270. $msg = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ];
  271. $signature = $key->sign($msg);
  272. $recid = $ec->getKeyRecoveryParam($msg, $signature, $key->getPublic());
  273. $r = $ec->recoverPubKey($msg, $signature, $recid);
  274. $this->assertTrue($key->getPublic()->eq($r), 'the keys should match');
  275. }
  276. public function test_should_fail_to_recover_key_when_no_quadratic_residue_available() {
  277. $ec = new \Elliptic\EC('secp256k1');
  278. $message =
  279. 'f75c6b18a72fabc0f0b888c3da58e004f0af1fe14f7ca5d8c897fe164925d5e9';
  280. $this->expectException(\Exception::class);
  281. $ec->recoverPubKey($message, [
  282. "r" => 'fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140',
  283. "s" => '8887321be575c8095f789dd4c743dfe42c1820f9231f98a962b210e3ac2452a3'
  284. ], 0);
  285. }
  286. }