[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * Curves over y^2 = x^3 + a*x + b 5 * 6 * These are curves used in SEC 2 over prime fields: http://www.secg.org/SEC2-Ver-1.0.pdf 7 * The curve is a weierstrass curve with a[1], a[3] and a[2] set to 0. 8 * 9 * Uses Jacobian Coordinates for speed if able: 10 * 11 * https://en.wikipedia.org/wiki/Jacobian_curve 12 * https://en.wikibooks.org/wiki/Cryptography/Prime_Curve/Jacobian_Coordinates 13 * 14 * PHP version 5 and 7 15 * 16 * @author Jim Wigginton <terrafrost@php.net> 17 * @copyright 2017 Jim Wigginton 18 * @license http://www.opensource.org/licenses/mit-license.html MIT License 19 * @link http://pear.php.net/package/Math_BigInteger 20 */ 21 22 namespace phpseclib3\Crypt\EC\BaseCurves; 23 24 use phpseclib3\Common\Functions\Strings; 25 use phpseclib3\Math\BigInteger; 26 use phpseclib3\Math\Common\FiniteField\Integer; 27 use phpseclib3\Math\PrimeField; 28 use phpseclib3\Math\PrimeField\Integer as PrimeInteger; 29 30 /** 31 * Curves over y^2 = x^3 + a*x + b 32 * 33 * @author Jim Wigginton <terrafrost@php.net> 34 */ 35 class Prime extends Base 36 { 37 /** 38 * Prime Field Integer factory 39 * 40 * @var \phpseclib3\Math\PrimeFields 41 */ 42 protected $factory; 43 44 /** 45 * Cofficient for x^1 46 * 47 * @var object 48 */ 49 protected $a; 50 51 /** 52 * Cofficient for x^0 53 * 54 * @var object 55 */ 56 protected $b; 57 58 /** 59 * Base Point 60 * 61 * @var object 62 */ 63 protected $p; 64 65 /** 66 * The number one over the specified finite field 67 * 68 * @var object 69 */ 70 protected $one; 71 72 /** 73 * The number two over the specified finite field 74 * 75 * @var object 76 */ 77 protected $two; 78 79 /** 80 * The number three over the specified finite field 81 * 82 * @var object 83 */ 84 protected $three; 85 86 /** 87 * The number four over the specified finite field 88 * 89 * @var object 90 */ 91 protected $four; 92 93 /** 94 * The number eight over the specified finite field 95 * 96 * @var object 97 */ 98 protected $eight; 99 100 /** 101 * The modulo 102 * 103 * @var BigInteger 104 */ 105 protected $modulo; 106 107 /** 108 * The Order 109 * 110 * @var BigInteger 111 */ 112 protected $order; 113 114 /** 115 * Sets the modulo 116 */ 117 public function setModulo(BigInteger $modulo) 118 { 119 $this->modulo = $modulo; 120 $this->factory = new PrimeField($modulo); 121 $this->two = $this->factory->newInteger(new BigInteger(2)); 122 $this->three = $this->factory->newInteger(new BigInteger(3)); 123 // used by jacobian coordinates 124 $this->one = $this->factory->newInteger(new BigInteger(1)); 125 $this->four = $this->factory->newInteger(new BigInteger(4)); 126 $this->eight = $this->factory->newInteger(new BigInteger(8)); 127 } 128 129 /** 130 * Set coefficients a and b 131 */ 132 public function setCoefficients(BigInteger $a, BigInteger $b) 133 { 134 if (!isset($this->factory)) { 135 throw new \RuntimeException('setModulo needs to be called before this method'); 136 } 137 $this->a = $this->factory->newInteger($a); 138 $this->b = $this->factory->newInteger($b); 139 } 140 141 /** 142 * Set x and y coordinates for the base point 143 * 144 * @param BigInteger|PrimeInteger $x 145 * @param BigInteger|PrimeInteger $y 146 * @return PrimeInteger[] 147 */ 148 public function setBasePoint($x, $y) 149 { 150 switch (true) { 151 case !$x instanceof BigInteger && !$x instanceof PrimeInteger: 152 throw new \UnexpectedValueException('Argument 1 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); 153 case !$y instanceof BigInteger && !$y instanceof PrimeInteger: 154 throw new \UnexpectedValueException('Argument 2 passed to Prime::setBasePoint() must be an instance of either BigInteger or PrimeField\Integer'); 155 } 156 if (!isset($this->factory)) { 157 throw new \RuntimeException('setModulo needs to be called before this method'); 158 } 159 $this->p = [ 160 $x instanceof BigInteger ? $this->factory->newInteger($x) : $x, 161 $y instanceof BigInteger ? $this->factory->newInteger($y) : $y 162 ]; 163 } 164 165 /** 166 * Retrieve the base point as an array 167 * 168 * @return array 169 */ 170 public function getBasePoint() 171 { 172 if (!isset($this->factory)) { 173 throw new \RuntimeException('setModulo needs to be called before this method'); 174 } 175 /* 176 if (!isset($this->p)) { 177 throw new \RuntimeException('setBasePoint needs to be called before this method'); 178 } 179 */ 180 return $this->p; 181 } 182 183 /** 184 * Adds two "fresh" jacobian form on the curve 185 * 186 * @return FiniteField[] 187 */ 188 protected function jacobianAddPointMixedXY(array $p, array $q) 189 { 190 list($u1, $s1) = $p; 191 list($u2, $s2) = $q; 192 if ($u1->equals($u2)) { 193 if (!$s1->equals($s2)) { 194 return []; 195 } else { 196 return $this->doublePoint($p); 197 } 198 } 199 $h = $u2->subtract($u1); 200 $r = $s2->subtract($s1); 201 $h2 = $h->multiply($h); 202 $h3 = $h2->multiply($h); 203 $v = $u1->multiply($h2); 204 $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two)); 205 $y3 = $r->multiply( 206 $v->subtract($x3) 207 )->subtract( 208 $s1->multiply($h3) 209 ); 210 return [$x3, $y3, $h]; 211 } 212 213 /** 214 * Adds one "fresh" jacobian form on the curve 215 * 216 * The second parameter should be the "fresh" one 217 * 218 * @return FiniteField[] 219 */ 220 protected function jacobianAddPointMixedX(array $p, array $q) 221 { 222 list($u1, $s1, $z1) = $p; 223 list($x2, $y2) = $q; 224 225 $z12 = $z1->multiply($z1); 226 227 $u2 = $x2->multiply($z12); 228 $s2 = $y2->multiply($z12->multiply($z1)); 229 if ($u1->equals($u2)) { 230 if (!$s1->equals($s2)) { 231 return []; 232 } else { 233 return $this->doublePoint($p); 234 } 235 } 236 $h = $u2->subtract($u1); 237 $r = $s2->subtract($s1); 238 $h2 = $h->multiply($h); 239 $h3 = $h2->multiply($h); 240 $v = $u1->multiply($h2); 241 $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two)); 242 $y3 = $r->multiply( 243 $v->subtract($x3) 244 )->subtract( 245 $s1->multiply($h3) 246 ); 247 $z3 = $h->multiply($z1); 248 return [$x3, $y3, $z3]; 249 } 250 251 /** 252 * Adds two jacobian coordinates on the curve 253 * 254 * @return FiniteField[] 255 */ 256 protected function jacobianAddPoint(array $p, array $q) 257 { 258 list($x1, $y1, $z1) = $p; 259 list($x2, $y2, $z2) = $q; 260 261 $z12 = $z1->multiply($z1); 262 $z22 = $z2->multiply($z2); 263 264 $u1 = $x1->multiply($z22); 265 $u2 = $x2->multiply($z12); 266 $s1 = $y1->multiply($z22->multiply($z2)); 267 $s2 = $y2->multiply($z12->multiply($z1)); 268 if ($u1->equals($u2)) { 269 if (!$s1->equals($s2)) { 270 return []; 271 } else { 272 return $this->doublePoint($p); 273 } 274 } 275 $h = $u2->subtract($u1); 276 $r = $s2->subtract($s1); 277 $h2 = $h->multiply($h); 278 $h3 = $h2->multiply($h); 279 $v = $u1->multiply($h2); 280 $x3 = $r->multiply($r)->subtract($h3)->subtract($v->multiply($this->two)); 281 $y3 = $r->multiply( 282 $v->subtract($x3) 283 )->subtract( 284 $s1->multiply($h3) 285 ); 286 $z3 = $h->multiply($z1)->multiply($z2); 287 return [$x3, $y3, $z3]; 288 } 289 290 /** 291 * Adds two points on the curve 292 * 293 * @return FiniteField[] 294 */ 295 public function addPoint(array $p, array $q) 296 { 297 if (!isset($this->factory)) { 298 throw new \RuntimeException('setModulo needs to be called before this method'); 299 } 300 301 if (!count($p) || !count($q)) { 302 if (count($q)) { 303 return $q; 304 } 305 if (count($p)) { 306 return $p; 307 } 308 return []; 309 } 310 311 // use jacobian coordinates 312 if (isset($p[2]) && isset($q[2])) { 313 if (isset($p['fresh']) && isset($q['fresh'])) { 314 return $this->jacobianAddPointMixedXY($p, $q); 315 } 316 if (isset($p['fresh'])) { 317 return $this->jacobianAddPointMixedX($q, $p); 318 } 319 if (isset($q['fresh'])) { 320 return $this->jacobianAddPointMixedX($p, $q); 321 } 322 return $this->jacobianAddPoint($p, $q); 323 } 324 325 if (isset($p[2]) || isset($q[2])) { 326 throw new \RuntimeException('Affine coordinates need to be manually converted to Jacobi coordinates or vice versa'); 327 } 328 329 if ($p[0]->equals($q[0])) { 330 if (!$p[1]->equals($q[1])) { 331 return []; 332 } else { // eg. doublePoint 333 list($numerator, $denominator) = $this->doublePointHelper($p); 334 } 335 } else { 336 $numerator = $q[1]->subtract($p[1]); 337 $denominator = $q[0]->subtract($p[0]); 338 } 339 $slope = $numerator->divide($denominator); 340 $x = $slope->multiply($slope)->subtract($p[0])->subtract($q[0]); 341 $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]); 342 343 return [$x, $y]; 344 } 345 346 /** 347 * Returns the numerator and denominator of the slope 348 * 349 * @return FiniteField[] 350 */ 351 protected function doublePointHelper(array $p) 352 { 353 $numerator = $this->three->multiply($p[0])->multiply($p[0])->add($this->a); 354 $denominator = $this->two->multiply($p[1]); 355 return [$numerator, $denominator]; 356 } 357 358 /** 359 * Doubles a jacobian coordinate on the curve 360 * 361 * @return FiniteField[] 362 */ 363 protected function jacobianDoublePoint(array $p) 364 { 365 list($x, $y, $z) = $p; 366 $x2 = $x->multiply($x); 367 $y2 = $y->multiply($y); 368 $z2 = $z->multiply($z); 369 $s = $this->four->multiply($x)->multiply($y2); 370 $m1 = $this->three->multiply($x2); 371 $m2 = $this->a->multiply($z2->multiply($z2)); 372 $m = $m1->add($m2); 373 $x1 = $m->multiply($m)->subtract($this->two->multiply($s)); 374 $y1 = $m->multiply($s->subtract($x1))->subtract( 375 $this->eight->multiply($y2->multiply($y2)) 376 ); 377 $z1 = $this->two->multiply($y)->multiply($z); 378 return [$x1, $y1, $z1]; 379 } 380 381 /** 382 * Doubles a "fresh" jacobian coordinate on the curve 383 * 384 * @return FiniteField[] 385 */ 386 protected function jacobianDoublePointMixed(array $p) 387 { 388 list($x, $y) = $p; 389 $x2 = $x->multiply($x); 390 $y2 = $y->multiply($y); 391 $s = $this->four->multiply($x)->multiply($y2); 392 $m1 = $this->three->multiply($x2); 393 $m = $m1->add($this->a); 394 $x1 = $m->multiply($m)->subtract($this->two->multiply($s)); 395 $y1 = $m->multiply($s->subtract($x1))->subtract( 396 $this->eight->multiply($y2->multiply($y2)) 397 ); 398 $z1 = $this->two->multiply($y); 399 return [$x1, $y1, $z1]; 400 } 401 402 /** 403 * Doubles a point on a curve 404 * 405 * @return FiniteField[] 406 */ 407 public function doublePoint(array $p) 408 { 409 if (!isset($this->factory)) { 410 throw new \RuntimeException('setModulo needs to be called before this method'); 411 } 412 413 if (!count($p)) { 414 return []; 415 } 416 417 // use jacobian coordinates 418 if (isset($p[2])) { 419 if (isset($p['fresh'])) { 420 return $this->jacobianDoublePointMixed($p); 421 } 422 return $this->jacobianDoublePoint($p); 423 } 424 425 list($numerator, $denominator) = $this->doublePointHelper($p); 426 427 $slope = $numerator->divide($denominator); 428 429 $x = $slope->multiply($slope)->subtract($p[0])->subtract($p[0]); 430 $y = $slope->multiply($p[0]->subtract($x))->subtract($p[1]); 431 432 return [$x, $y]; 433 } 434 435 /** 436 * Returns the X coordinate and the derived Y coordinate 437 * 438 * @return array 439 */ 440 public function derivePoint($m) 441 { 442 $y = ord(Strings::shift($m)); 443 $x = new BigInteger($m, 256); 444 $xp = $this->convertInteger($x); 445 switch ($y) { 446 case 2: 447 $ypn = false; 448 break; 449 case 3: 450 $ypn = true; 451 break; 452 default: 453 throw new \RuntimeException('Coordinate not in recognized format'); 454 } 455 $temp = $xp->multiply($this->a); 456 $temp = $xp->multiply($xp)->multiply($xp)->add($temp); 457 $temp = $temp->add($this->b); 458 $b = $temp->squareRoot(); 459 if (!$b) { 460 throw new \RuntimeException('Unable to derive Y coordinate'); 461 } 462 $bn = $b->isOdd(); 463 $yp = $ypn == $bn ? $b : $b->negate(); 464 return [$xp, $yp]; 465 } 466 467 /** 468 * Tests whether or not the x / y values satisfy the equation 469 * 470 * @return boolean 471 */ 472 public function verifyPoint(array $p) 473 { 474 list($x, $y) = $p; 475 $lhs = $y->multiply($y); 476 $temp = $x->multiply($this->a); 477 $temp = $x->multiply($x)->multiply($x)->add($temp); 478 $rhs = $temp->add($this->b); 479 480 return $lhs->equals($rhs); 481 } 482 483 /** 484 * Returns the modulo 485 * 486 * @return \phpseclib3\Math\BigInteger 487 */ 488 public function getModulo() 489 { 490 return $this->modulo; 491 } 492 493 /** 494 * Returns the a coefficient 495 * 496 * @return \phpseclib3\Math\PrimeField\Integer 497 */ 498 public function getA() 499 { 500 return $this->a; 501 } 502 503 /** 504 * Returns the a coefficient 505 * 506 * @return \phpseclib3\Math\PrimeField\Integer 507 */ 508 public function getB() 509 { 510 return $this->b; 511 } 512 513 /** 514 * Multiply and Add Points 515 * 516 * Adapted from: 517 * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L125 518 * 519 * @return int[] 520 */ 521 public function multiplyAddPoints(array $points, array $scalars) 522 { 523 $length = count($points); 524 525 foreach ($points as &$point) { 526 $point = $this->convertToInternal($point); 527 } 528 529 $wnd = [$this->getNAFPoints($points[0], 7)]; 530 $wndWidth = [isset($points[0]['nafwidth']) ? $points[0]['nafwidth'] : 7]; 531 for ($i = 1; $i < $length; $i++) { 532 $wnd[] = $this->getNAFPoints($points[$i], 1); 533 $wndWidth[] = isset($points[$i]['nafwidth']) ? $points[$i]['nafwidth'] : 1; 534 } 535 536 $naf = []; 537 538 // comb all window NAFs 539 540 $max = 0; 541 for ($i = $length - 1; $i >= 1; $i -= 2) { 542 $a = $i - 1; 543 $b = $i; 544 if ($wndWidth[$a] != 1 || $wndWidth[$b] != 1) { 545 $naf[$a] = $scalars[$a]->getNAF($wndWidth[$a]); 546 $naf[$b] = $scalars[$b]->getNAF($wndWidth[$b]); 547 $max = max(count($naf[$a]), count($naf[$b]), $max); 548 continue; 549 } 550 551 $comb = [ 552 $points[$a], // 1 553 null, // 3 554 null, // 5 555 $points[$b] // 7 556 ]; 557 558 $comb[1] = $this->addPoint($points[$a], $points[$b]); 559 $comb[2] = $this->addPoint($points[$a], $this->negatePoint($points[$b])); 560 561 $index = [ 562 -3, /* -1 -1 */ 563 -1, /* -1 0 */ 564 -5, /* -1 1 */ 565 -7, /* 0 -1 */ 566 0, /* 0 -1 */ 567 7, /* 0 1 */ 568 5, /* 1 -1 */ 569 1, /* 1 0 */ 570 3 /* 1 1 */ 571 ]; 572 573 $jsf = self::getJSFPoints($scalars[$a], $scalars[$b]); 574 575 $max = max(count($jsf[0]), $max); 576 if ($max > 0) { 577 $naf[$a] = array_fill(0, $max, 0); 578 $naf[$b] = array_fill(0, $max, 0); 579 } else { 580 $naf[$a] = []; 581 $naf[$b] = []; 582 } 583 584 for ($j = 0; $j < $max; $j++) { 585 $ja = isset($jsf[0][$j]) ? $jsf[0][$j] : 0; 586 $jb = isset($jsf[1][$j]) ? $jsf[1][$j] : 0; 587 588 $naf[$a][$j] = $index[3 * ($ja + 1) + $jb + 1]; 589 $naf[$b][$j] = 0; 590 $wnd[$a] = $comb; 591 } 592 } 593 594 $acc = []; 595 $temp = [0, 0, 0, 0]; 596 for ($i = $max; $i >= 0; $i--) { 597 $k = 0; 598 while ($i >= 0) { 599 $zero = true; 600 for ($j = 0; $j < $length; $j++) { 601 $temp[$j] = isset($naf[$j][$i]) ? $naf[$j][$i] : 0; 602 if ($temp[$j] != 0) { 603 $zero = false; 604 } 605 } 606 if (!$zero) { 607 break; 608 } 609 $k++; 610 $i--; 611 } 612 613 if ($i >= 0) { 614 $k++; 615 } 616 while ($k--) { 617 $acc = $this->doublePoint($acc); 618 } 619 620 if ($i < 0) { 621 break; 622 } 623 624 for ($j = 0; $j < $length; $j++) { 625 $z = $temp[$j]; 626 $p = null; 627 if ($z == 0) { 628 continue; 629 } 630 $p = $z > 0 ? 631 $wnd[$j][($z - 1) >> 1] : 632 $this->negatePoint($wnd[$j][(-$z - 1) >> 1]); 633 $acc = $this->addPoint($acc, $p); 634 } 635 } 636 637 return $this->convertToAffine($acc); 638 } 639 640 /** 641 * Precomputes NAF points 642 * 643 * Adapted from: 644 * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/curve/base.js#L351 645 * 646 * @return int[] 647 */ 648 private function getNAFPoints(array $point, $wnd) 649 { 650 if (isset($point['naf'])) { 651 return $point['naf']; 652 } 653 654 $res = [$point]; 655 $max = (1 << $wnd) - 1; 656 $dbl = $max == 1 ? null : $this->doublePoint($point); 657 for ($i = 1; $i < $max; $i++) { 658 $res[] = $this->addPoint($res[$i - 1], $dbl); 659 } 660 661 $point['naf'] = $res; 662 663 /* 664 $str = ''; 665 foreach ($res as $re) { 666 $re[0] = bin2hex($re[0]->toBytes()); 667 $re[1] = bin2hex($re[1]->toBytes()); 668 $str.= " ['$re[0]', '$re[1]'],\r\n"; 669 } 670 file_put_contents('temp.txt', $str); 671 exit; 672 */ 673 674 return $res; 675 } 676 677 /** 678 * Precomputes points in Joint Sparse Form 679 * 680 * Adapted from: 681 * https://github.com/indutny/elliptic/blob/725bd91/lib/elliptic/utils.js#L96 682 * 683 * @return int[] 684 */ 685 private static function getJSFPoints(Integer $k1, Integer $k2) 686 { 687 static $three; 688 if (!isset($three)) { 689 $three = new BigInteger(3); 690 } 691 692 $jsf = [[], []]; 693 $k1 = $k1->toBigInteger(); 694 $k2 = $k2->toBigInteger(); 695 $d1 = 0; 696 $d2 = 0; 697 698 while ($k1->compare(new BigInteger(-$d1)) > 0 || $k2->compare(new BigInteger(-$d2)) > 0) { 699 // first phase 700 $m14 = $k1->testBit(0) + 2 * $k1->testBit(1); 701 $m14 += $d1; 702 $m14 &= 3; 703 704 $m24 = $k2->testBit(0) + 2 * $k2->testBit(1); 705 $m24 += $d2; 706 $m24 &= 3; 707 708 if ($m14 == 3) { 709 $m14 = -1; 710 } 711 if ($m24 == 3) { 712 $m24 = -1; 713 } 714 715 $u1 = 0; 716 if ($m14 & 1) { // if $m14 is odd 717 $m8 = $k1->testBit(0) + 2 * $k1->testBit(1) + 4 * $k1->testBit(2); 718 $m8 += $d1; 719 $m8 &= 7; 720 $u1 = ($m8 == 3 || $m8 == 5) && $m24 == 2 ? -$m14 : $m14; 721 } 722 $jsf[0][] = $u1; 723 724 $u2 = 0; 725 if ($m24 & 1) { // if $m24 is odd 726 $m8 = $k2->testBit(0) + 2 * $k2->testBit(1) + 4 * $k2->testBit(2); 727 $m8 += $d2; 728 $m8 &= 7; 729 $u2 = ($m8 == 3 || $m8 == 5) && $m14 == 2 ? -$m24 : $m24; 730 } 731 $jsf[1][] = $u2; 732 733 // second phase 734 if (2 * $d1 == $u1 + 1) { 735 $d1 = 1 - $d1; 736 } 737 if (2 * $d2 == $u2 + 1) { 738 $d2 = 1 - $d2; 739 } 740 $k1 = $k1->bitwise_rightShift(1); 741 $k2 = $k2->bitwise_rightShift(1); 742 } 743 744 return $jsf; 745 } 746 747 /** 748 * Returns the affine point 749 * 750 * A Jacobian Coordinate is of the form (x, y, z). 751 * To convert a Jacobian Coordinate to an Affine Point 752 * you do (x / z^2, y / z^3) 753 * 754 * @return \phpseclib3\Math\PrimeField\Integer[] 755 */ 756 public function convertToAffine(array $p) 757 { 758 if (!isset($p[2])) { 759 return $p; 760 } 761 list($x, $y, $z) = $p; 762 $z = $this->one->divide($z); 763 $z2 = $z->multiply($z); 764 return [ 765 $x->multiply($z2), 766 $y->multiply($z2)->multiply($z) 767 ]; 768 } 769 770 /** 771 * Converts an affine point to a jacobian coordinate 772 * 773 * @return \phpseclib3\Math\PrimeField\Integer[] 774 */ 775 public function convertToInternal(array $p) 776 { 777 if (isset($p[2])) { 778 return $p; 779 } 780 781 $p[2] = clone $this->one; 782 $p['fresh'] = true; 783 return $p; 784 } 785 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body