Overview

Namespaces

  • Liberty
  • None

Classes

  • Liberty\BigInteger
  • Liberty\Block
  • Liberty\Blockchain
  • Liberty\Collection
  • Liberty\CollectionFileList
  • Liberty\File
  • Liberty\Folder
  • Liberty\INI
  • Liberty\LECDSA
  • Liberty\Onecrypt
  • Liberty\SSV
  • Liberty\Text
  • Liberty\Transaction
  • Liberty\Wallet
  • Liberty\WebPeer

Functions

  • __autoload
  • Overview
  • Namespace
  • Class
   1: <?php
   2: 
   3: namespace Liberty;
   4: 
   5: use Liberty\BigInteger;
   6: 
   7: 
   8: /**
   9:  * Onecrypt. An Autonomous Liberty Eliptic Curve and Digital Signature Algorithm. (In Development)
  10:  * 
  11:  * Copyright (C) Liberty Group <cryptolibertygroup@gmail.com>.
  12:  * 
  13:  * Unlicense. Public Domain. http://unlicense.org/ 
  14:  * This piece of software is provided without warranty of any kind, use it at your own risk.
  15:  *
  16:  * @category  Cryptocurrency
  17:  * @package   Liberty
  18:  * @license   http://unlicense.org Unlicense
  19:  * @version   1.0.0
  20:  * @since     2017-03-25
  21:  * @author    Liberty Group <cryptolibertygroup@gmail.com>, Jan Moritz Lindemann
  22:  */
  23: 
  24: 
  25: class Onecrypt
  26: {
  27: 
  28:     public $k;
  29:     public $a;
  30:     public $b;
  31:     public $p;
  32:     public $n;
  33:     public $G;
  34:     public $networkPrefix;
  35: 
  36: 
  37: 
  38:     public function __construct()
  39:     {
  40:         $this->a = new BigInteger("0", 10);
  41:         $this->b = new BigInteger("7", 10);
  42:         $this->p = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F", 16);
  43:         $this->n = new BigInteger("FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141", 16);
  44: 
  45:         $this->G = [
  46:                     'x' => new BigInteger('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
  47:                     'y' => new BigInteger('32670510020758816978083085130507043184471273380659243275938904335757337482424')
  48:                    ];
  49: 
  50:         // Prefix: 48 (0x30) Char 'L'
  51:         $this->networkPrefix = '30';
  52:     }
  53: 
  54: 
  55: 
  56: 
  57:     /***
  58:      * Convert a number to a compact Int
  59:      * taken from https://github.com/scintill/php-bitcoin-signature-routines/blob/master/verifymessage.php
  60:      *
  61:      * @param int $i
  62:      * @return string (bin)
  63:      * @throws \Exception
  64:      */
  65:     public function numToVarIntString($i) {
  66:         if ($i < 0xfd) {
  67:             return chr($i);
  68:         } else if ($i <= 0xffff) {
  69:             return pack('Cv', 0xfd, $i);
  70:         } else if ($i <= 0xffffffff) {
  71:             return pack('CV', 0xfe, $i);
  72:         } else {
  73:             throw new \Exception('int too large');
  74:         }
  75:     }
  76: 
  77: 
  78: 
  79: 
  80:     /***
  81:      * Set the network prefix, '00' = main network, '6f' = test network.
  82:      *
  83:      * @param string $prefix (hexa)
  84:      */
  85:     public function setNetworkPrefix($prefix)
  86:     {
  87:         $this->networkPrefix = $prefix;
  88:     }
  89: 
  90: 
  91: 
  92: 
  93:     /**
  94:      * Returns the current network prefix, '00' = main network, '6f' = test network.
  95:      *
  96:      * @return string (hexa)
  97:      */
  98:     public function getNetworkPrefix()
  99:     {
 100:         return $this->networkPrefix;
 101:     }
 102: 
 103: 
 104: 
 105:     /**
 106:      * Returns the current network prefix for WIF, '80' = main network, 'ef' = test network.
 107:      *
 108:      * @return string (hexa)
 109:      */
 110:     public function getPrivatePrefix(){
 111:         if($this->networkPrefix =='6f')
 112:             return 'ef';
 113:         else
 114:             return 'b0'; //80
 115:     }
 116: 
 117: 
 118: 
 119: 
 120:     /***
 121:      * Permutation table used for Base58 encoding and decoding.
 122:      *
 123:      * @param string $char
 124:      * @param bool $reverse
 125:      * @return string|null
 126:      */
 127:     public function base58_permutation($char, $reverse = false)
 128:     {
 129:         $table = [
 130:                   '1','2','3','4','5','6','7','8','9','A','B','C','D',
 131:                   'E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W',
 132:                   'X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','m','n','o',
 133:                   'p','q','r','s','t','u','v','w','x','y','z'
 134:                  ];
 135: 
 136:         if($reverse)
 137:         {
 138:             $reversedTable = [];
 139:             foreach($table as $key => $element)
 140:             {
 141:                 $reversedTable[$element] = $key;
 142:             }
 143: 
 144:             if(isset($reversedTable[$char]))
 145:                 return $reversedTable[$char];
 146:             else
 147:                 return null;
 148:         }
 149: 
 150:         if(isset($table[$char]))
 151:             return $table[$char];
 152:         else
 153:             return null;
 154:     }
 155: 
 156: 
 157: 
 158: 
 159:     /***
 160:      * Bitcoin standard 256 bit hash function : double sha256
 161:      *
 162:      * @param string $data
 163:      * @return string (hexa)
 164:      */
 165:     public function hash256($data)
 166:     {
 167:         return hash('sha256', hex2bin(hash('sha256', $data)));
 168:     }
 169: 
 170: 
 171: 
 172: 
 173:     /**
 174:      * @param string $data
 175:      * @return string (hexa)
 176:      */
 177:     public function hash160($data)
 178:     {
 179:         return hash('ripemd160', hex2bin(hash('sha256', $data)));
 180:     }
 181: 
 182: 
 183: 
 184: 
 185:     /**
 186:      * Generates a random 256 bytes hexadecimal encoded string that is smaller than n
 187:      *
 188:      * @return string (hexa)
 189:      * @throws \Exception
 190:      */
 191:     public function generateRandom256BitsHexaString()
 192:     {
 193:         do
 194:         {
 195:             $bytes = openssl_random_pseudo_bytes(256, $cStrong);
 196:             $hex = bin2hex($bytes);
 197:             $random = $hex . microtime(true) . hash("sha256", microtime());
 198: 
 199:             if ($cStrong === false) {
 200:                 throw new \Exception('Your system is not able to generate strong enough random numbers');
 201:             }
 202:             $res = $this->hash256($random);
 203: 
 204:             $init1 = new BigInteger($res, 16);
 205: 
 206:         } while($init1->compare($this->n->subtract(new BigInteger(1, 10))) === 1); // make sure the generate string is smaller than n
 207: 
 208:         return $res;
 209:     }
 210: 
 211: 
 212: 
 213: 
 214:     /***
 215:      * encode a hexadecimal string in Base58.
 216:      *
 217:      * @param string $data (hexa)
 218:      * @param bool $littleEndian
 219:      * @return string (base58)
 220:      * @throws \Exception
 221:      */
 222:     public function base58_encode($data, $littleEndian = true)
 223:     {
 224:         $res = "";
 225:         $dataIntVal = new BigInteger($data, 16);
 226: 
 227:         while($dataIntVal->compare(new BigInteger(0, 10)) > 0)
 228:         {
 229:             $qr = $dataIntVal->divide(new BigInteger(58, 10));
 230:             $dataIntVal = $qr[0];
 231:             $reminder = $qr[1]->toString();
 232:             if(!$this->base58_permutation($reminder))
 233:             {
 234:                 throw new \Exception('Something went wrong during base58 encoding');
 235:             }
 236:             $res .= $this->base58_permutation($reminder);
 237:         }
 238: 
 239:         //get number of leading zeros
 240:         $leading = '';
 241:         $i = 0;
 242:         while(substr($data, $i, 1) === '0')
 243:         {
 244:             if($i!== 0 && $i%2)
 245:             {
 246:                 $leading .= '1';
 247:             }
 248:             $i++;
 249:         }
 250: 
 251:         if($littleEndian)
 252:             return strrev($res . $leading);
 253:         else
 254:             return $res.$leading;
 255:     }
 256: 
 257: 
 258: 
 259: 
 260:     /***
 261:      * Decode a Base58 encoded string and returns it's value as a hexadecimal string
 262:      *
 263:      * @param string $encodedData (base58)
 264:      * @param bool $littleEndian
 265:      * @return string (hexa)
 266:      */
 267:     public function base58_decode($encodedData, $littleEndian = true)
 268:     {
 269:         $res = new BigInteger(0, 10);
 270:         $length = strlen($encodedData);
 271:         if($littleEndian)
 272:         {
 273:             $encodedData = strrev($encodedData);
 274:         }
 275: 
 276:         for($i = $length - 1; $i >= 0; $i--)
 277:         {
 278:             $mult = $res->multiply(new BigInteger(58, 10));
 279:             $perm = new BigInteger($this->base58_permutation(substr($encodedData, $i, 1), true));
 280:             $res = $mult->add($perm);
 281:         }
 282: 
 283:         $res = $res->toHex();
 284:         $i = $length - 1;
 285:         while(substr($encodedData, $i, 1) === '1')
 286:         {
 287:             $res = '00' . $res;
 288:             $i--;
 289:         }
 290: 
 291:         if(strlen($res)%2 !== 0)
 292:         {
 293:             $res = '0' . $res;
 294:         }
 295: 
 296:         return $res;
 297:     }
 298: 
 299: 
 300: 
 301: 
 302:     /***
 303:      * Computes the result of a point addition and returns the resulting point as an Array.
 304:      *
 305:      * @param Array $pt
 306:      * @return Array Point
 307:      * @throws \Exception
 308:      */
 309:     public function doublePoint(Array $pt)
 310:     {
 311:         $a = $this->a;
 312:         $p = $this->p;
 313: 
 314:         $init = new BigInteger(2, 10);
 315:         $mult = $init->multiply($pt['y']);
 316:         $mod = $mult->mod($p);
 317:         $gcd = $mod->gcd($p);
 318: 
 319:         if($gcd->toString() !== "1")
 320:         {
 321:             throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
 322:         }
 323: 
 324:         // SLOPE = (3 * ptX^2 + a )/( 2*ptY )
 325:         // Equals (3 * ptX^2 + a ) * ( 2*ptY )^-1
 326: 
 327: 
 328: 
 329: 
 330:         /*
 331: 
 332:         $slope = gmp_mod(
 333:                          gmp_mul(
 334:                                  gmp_invert(
 335:                                             gmp_mod(
 336:                                                     gmp_mul(
 337:                                                             gmp_init(2, 10),
 338:                                                             $pt['y']
 339:                                                     ),
 340:                                                     $p
 341:                                             ),
 342:                                             $p
 343:                                  ),
 344:                                  gmp_add(
 345:                                          gmp_mul(
 346:                                                  gmp_init(3, 10),
 347:                                                  gmp_pow($pt['x'], 2)
 348:                                          ),
 349:                                          $a
 350:                                  )
 351:                          ),
 352:                          $p
 353:                 );
 354: 
 355:         // nPtX = slope^2 - 2 * ptX
 356:         // Equals slope^2 - ptX - ptX
 357:         $nPt = [];
 358:         $nPt['x'] = gmp_mod(
 359:                             gmp_sub(
 360:                                     gmp_sub(
 361:                                             gmp_pow($slope, 2),
 362:                                             $pt['x']
 363:                                     ),
 364:                                     $pt['x']
 365:                             ),
 366:                             $p
 367:                     );
 368: 
 369:         // nPtY = slope * (ptX - nPtx) - ptY
 370:         $nPt['y'] = gmp_mod(
 371:                             gmp_sub(
 372:                                     gmp_mul(
 373:                                             $slope,
 374:                                             gmp_sub(
 375:                                                     $pt['x'],
 376:                                                     $nPt['x']
 377:                                             )
 378:                                     ),
 379:                                     $pt['y']
 380:                             ),
 381:                             $p
 382:                     );
 383: 
 384:         return $nPt;
 385:         */
 386:     }
 387: 
 388: 
 389: 
 390: 
 391:     /***
 392:      * Computes the result of a point addition and returns the resulting point as an Array.
 393:      *
 394:      * @param Array $pt1
 395:      * @param Array $pt2
 396:      * @return Array Point
 397:      * @throws \Exception
 398:      */
 399:     public function addPoints(Array $pt1, Array $pt2)
 400:     {
 401:         $p = $this->p;
 402:         if(gmp_cmp($pt1['x'], $pt2['x']) === 0  && gmp_cmp($pt1['y'], $pt2['y']) === 0) //if identical
 403:         {
 404:             return $this->doublePoint($pt1);
 405:         }
 406: 
 407:         $gcd = gmp_strval(gmp_gcd(gmp_sub($pt1['x'], $pt2['x']), $p));
 408:         if($gcd !== '1')
 409:         {
 410:             throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
 411:         }
 412: 
 413:         // SLOPE = (pt1Y - pt2Y)/( pt1X - pt2X )
 414:         // Equals (pt1Y - pt2Y) * ( pt1X - pt2X )^-1
 415:         $slope      = gmp_mod(
 416:                               gmp_mul(
 417:                                       gmp_sub(
 418:                                               $pt1['y'],
 419:                                               $pt2['y']
 420:                                       ),
 421:                                       gmp_invert(
 422:                                                  gmp_sub(
 423:                                                          $pt1['x'],
 424:                                                          $pt2['x']
 425:                                                  ),
 426:                                                  $p
 427:                                       )
 428:                               ),
 429:                               $p
 430:                       );
 431: 
 432:         // nPtX = slope^2 - ptX1 - ptX2
 433:         $nPt = [];
 434:         $nPt['x']   = gmp_mod(
 435:                               gmp_sub(
 436:                                       gmp_sub(
 437:                                               gmp_pow($slope, 2),
 438:                                               $pt1['x']
 439:                                       ),
 440:                                       $pt2['x']
 441:                               ),
 442:                               $p
 443:                       );
 444: 
 445:         // nPtX = slope * (ptX1 - nPtX) - ptY1
 446:         $nPt['y']   = gmp_mod(
 447:                               gmp_sub(
 448:                                       gmp_mul(
 449:                                               $slope,
 450:                                               gmp_sub(
 451:                                                       $pt1['x'],
 452:                                                       $nPt['x']
 453:                                               )
 454:                                       ),
 455:                                       $pt1['y']
 456:                               ),
 457:                               $p
 458:                       );
 459: 
 460:         return $nPt;
 461:     }
 462: 
 463: 
 464: 
 465: 
 466:     /***
 467:      * Computes the result of a point multiplication and returns the resulting point as an Array.
 468:      *
 469:      * @param string|resource $k (hexa|GMP|Other bases definded in base)
 470:      * @param Array $pG
 471:      * @param $base
 472:      * @throws \Exception
 473:      * @return Array Point
 474:      */
 475:     public function mulPoint($k, Array $pG, $base = null)
 476:     {
 477:         //in order to calculate k*G
 478:         if($base === 16 || $base === null || is_resource($base))
 479:             $k = gmp_init($k, 16);
 480:         if($base === 10)
 481:             $k = gmp_init($k, 10);
 482:         $kBin = gmp_strval($k, 2);
 483: 
 484:         $lastPoint = $pG;
 485:         for($i = 1; $i < strlen($kBin); $i++)
 486:         {
 487:             if(substr($kBin, $i, 1) === '1')
 488:             {
 489:                 $dPt = $this->doublePoint($lastPoint);
 490:                 $lastPoint = $this->addPoints($dPt, $pG);
 491:             }
 492:             else
 493:             {
 494:                 $lastPoint = $this->doublePoint($lastPoint);
 495:             }
 496:         }
 497:         if(!$this->validatePoint(gmp_strval($lastPoint['x'], 16), gmp_strval($lastPoint['y'], 16)))
 498:             throw new \Exception('The resulting point is not on the curve.');
 499:         return $lastPoint;
 500:     }
 501: 
 502: 
 503: 
 504: 
 505:     /***
 506:      * Calculates the square root of $a mod p and returns the 2 solutions as an array.
 507:      *
 508:      * @param resource $a (GMP)
 509:      * @return array|null
 510:      * @throws \Exception
 511:      */
 512:     public function sqrt($a)
 513:     {
 514:         $p = $this->p;
 515: 
 516:         if(gmp_legendre($a, $p) !== 1)
 517:         {
 518:             //no result
 519:             return null;
 520:         }
 521: 
 522:         if(gmp_strval(gmp_mod($p, gmp_init(4, 10)), 10) === '3')
 523:         {
 524:             $sqrt1 = gmp_powm(
 525:                             $a,
 526:                             gmp_div_q(
 527:                                 gmp_add($p, gmp_init(1, 10)),
 528:                                 gmp_init(4, 10)
 529:                             ),
 530:                             $p
 531:                     );
 532:             // there are always 2 results for a square root
 533:             // In an infinite number field you have -2^2 = 2^2 = 4
 534:             // In a finite number field you have a^2 = (p-a)^2
 535:             $sqrt2 = gmp_mod(gmp_sub($p, $sqrt1), $p);
 536:             return [$sqrt1, $sqrt2];
 537:         }
 538:         else
 539:         {
 540:             throw new \Exception('P % 4 != 3 , this isn\'t supported yet.');
 541:         }
 542:     }
 543: 
 544: 
 545: 
 546: 
 547:     /***
 548:      * Calculate the Y coordinates for a given X coordinate.
 549:      *
 550:      * @param string $x (hexa)
 551:      * @param null $derEvenOrOddCode
 552:      * @return array|null|String
 553:      */
 554:     public function calculateYWithX($x, $derEvenOrOddCode = null)
 555:     {
 556:         $a  = $this->a;
 557:         $b  = $this->b;
 558:         $p  = $this->p;
 559: 
 560:         $x  = gmp_init($x, 16);
 561:         $y2 = gmp_mod(
 562:                       gmp_add(
 563:                               gmp_add(
 564:                                       gmp_powm($x, gmp_init(3, 10), $p),
 565:                                       gmp_mul($a, $x)
 566:                               ),
 567:                               $b
 568:                       ),
 569:                       $p
 570:               );
 571: 
 572:         $y = $this->sqrt($y2);
 573: 
 574:         if($y === null) //if there is no result
 575:         {
 576:             return null;
 577:         }
 578: 
 579:         if($derEvenOrOddCode === null)
 580:         {
 581:             return $y;
 582:         }
 583: 
 584:         else if($derEvenOrOddCode === '02') // even
 585:         {
 586:             $resY = null;
 587:             if(gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) === '0')
 588:                 $resY = gmp_strval($y[0], 16);
 589:             if(gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) === '0')
 590:                 $resY = gmp_strval($y[1], 16);
 591:             if($resY !== null)
 592:             {
 593:                 while(strlen($resY) < 64)
 594:                 {
 595:                     $resY = '0' . $resY;
 596:                 }
 597:             }
 598:             return $resY;
 599:         }
 600:         else if($derEvenOrOddCode === '03') // odd
 601:         {
 602:             $resY = null;
 603:             if(gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) === '1')
 604:                 $resY = gmp_strval($y[0], 16);
 605:             if(gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) === '1')
 606:                 $resY = gmp_strval($y[1], 16);
 607:             if($resY !== null)
 608:             {
 609:                 while(strlen($resY) < 64)
 610:                 {
 611:                     $resY = '0' . $resY;
 612:                 }
 613:             }
 614:             return $resY;
 615:         }
 616: 
 617:         return null;
 618:     }
 619: 
 620: 
 621: 
 622: 
 623:     /***
 624:      * returns the public key coordinates as an array.
 625:      *
 626:      * @param string $derPubKey (hexa)
 627:      * @return array
 628:      * @throws \Exception
 629:      */
 630:     public function getPubKeyPointsWithDerPubKey($derPubKey)
 631:     {
 632:         if(substr($derPubKey, 0, 2) === '04' && strlen($derPubKey) === 130)
 633:         {
 634:             //uncompressed der encoded public key
 635:             $x = substr($derPubKey, 2, 64);
 636:             $y = substr($derPubKey, 66, 64);
 637:             return ['x' => $x, 'y' => $y];
 638:         }
 639:         else if((substr($derPubKey, 0, 2) === '02' || substr($derPubKey, 0, 2) === '03') && strlen($derPubKey) === 66)
 640:         {
 641:             //compressed der encoded public key
 642:             $x = substr($derPubKey, 2, 64);
 643:             $y = $this->calculateYWithX($x, substr($derPubKey, 0, 2));
 644:             return ['x' => $x, 'y' => $y];
 645:         }
 646:         else
 647:         {
 648:             throw new \Exception('Invalid derPubKey format : ' . $derPubKey);
 649:         }
 650:     }
 651: 
 652: 
 653: 
 654: 
 655:     /**
 656:      * @param array $pubKey (array <x:string, y:string>)
 657:      * @param bool $compressed
 658:      * @return string
 659:      */
 660:     public function getDerPubKeyWithPubKeyPoints($pubKey, $compressed = true)
 661:     {
 662:         if($compressed === false)
 663:         {
 664:             return '04' . $pubKey['x'] . $pubKey['y'];
 665:         }
 666:         else
 667:         {
 668:             if(gmp_strval(gmp_mod(gmp_init($pubKey['y'], 16), gmp_init(2, 10))) === '0')
 669:                 $pubKey = '02' . $pubKey['x'];  //if $pubKey['y'] is even
 670:             else
 671:                 $pubKey = '03' . $pubKey['x'];  //if $pubKey['y'] is odd
 672: 
 673:             return $pubKey;
 674:         }
 675:     }
 676: 
 677: 
 678: 
 679: 
 680:     /***
 681:      * Returns true if the point is on the curve and false if it isn't.
 682:      *
 683:      * @param string $x (hexa)
 684:      * @param string $y (hexa)
 685:      * @return bool
 686:      */
 687:     public function validatePoint($x, $y)
 688:     {
 689:         $a  = $this->a;
 690:         $b  = $this->b;
 691:         $p  = $this->p;
 692: 
 693:         $x  = gmp_init($x, 16);
 694:         $y2 = gmp_mod(
 695:                         gmp_add(
 696:                             gmp_add(
 697:                                 gmp_powm($x, gmp_init(3, 10), $p),
 698:                                 gmp_mul($a, $x)
 699:                             ),
 700:                             $b
 701:                         ),
 702:                         $p
 703:                     );
 704:         $y = gmp_mod(gmp_pow(gmp_init($y, 16), 2), $p);
 705: 
 706:         if(gmp_cmp($y2, $y) === 0)
 707:             return true;
 708:         else
 709:             return false;
 710:     }
 711: 
 712: 
 713: 
 714: 
 715: 
 716:     /***
 717:      * returns the X and Y point coordinates of the public key.
 718:      *
 719:      * @return Array Point
 720:      * @throws \Exception
 721:      */
 722:     public function getPubKeyPoints()
 723:     {
 724:         $G = $this->G;
 725:         $k = $this->k;
 726: 
 727:         if(!isset($this->k))
 728:         {
 729:             throw new \Exception('No Private Key was defined');
 730:         }
 731: 
 732:         $pubKey = $this->mulPoint(
 733:                                           $k,
 734:                                           ['x' => $G['x'], 'y' => $G['y']]
 735:                                  );
 736: 
 737:         $pubKey['x'] = gmp_strval($pubKey['x'], 16);
 738:         $pubKey['y'] = gmp_strval($pubKey['y'], 16);
 739: 
 740:         while(strlen($pubKey['x']) < 64)
 741:         {
 742:             $pubKey['x'] = '0' . $pubKey['x'];
 743:         }
 744: 
 745:         while(strlen($pubKey['y']) < 64)
 746:         {
 747:             $pubKey['y'] = '0' . $pubKey['y'];
 748:         }
 749: 
 750:         return $pubKey;
 751:     }
 752: 
 753: 
 754: 
 755: 
 756:     /***
 757:      * returns the uncompressed DER encoded public key.
 758:      *
 759:      * @param array $pubKeyPts (array <x:string, y:string>)
 760:      * @return string (hexa)
 761:      * @throws \Exception
 762:      */
 763:     public function getUncompressedPubKey(array $pubKeyPts = [])
 764:     {
 765:         if(empty($pubKeyPts))
 766:             $pubKeyPts = $this->getPubKeyPoints();
 767:         $uncompressedPubKey = '04' . $pubKeyPts['x'] . $pubKeyPts['y'];
 768: 
 769:         return $uncompressedPubKey;
 770:     }
 771: 
 772: 
 773: 
 774: 
 775:     /***
 776:      * returns the compressed DER encoded public key.
 777:      *
 778:      * @param array $pubKeyPts (array <x:string, y:string>)
 779:      * @return array|string
 780:      * @throws \Exception
 781:      */
 782:     public function getPubKey(array $pubKeyPts = [])
 783:     {
 784:         if(empty($pubKeyPts))
 785:             $pubKeyPts = $this->getPubKeyPoints();
 786: 
 787:         if(gmp_strval(gmp_mod(gmp_init($pubKeyPts['y'], 16), gmp_init(2, 10))) === '0')
 788:             $compressedPubKey = '02' . $pubKeyPts['x']; //if $pubKey['y'] is even
 789:         else
 790:             $compressedPubKey = '03' . $pubKeyPts['x']; //if $pubKey['y'] is odd
 791: 
 792:         return $compressedPubKey;
 793:     }
 794: 
 795: 
 796: 
 797: 
 798:     /***
 799:      * returns the uncompressed Bitcoin address generated from the private key if $compressed is false and
 800:      * the compressed if $compressed is true.
 801:      *
 802:      * @param bool $compressed
 803:      * @param string $derPubKey (hexa)
 804:      * @throws \Exception
 805:      * @return String Base58
 806:      */
 807:     public function getUncompressedAddress($compressed = false, $derPubKey = null)
 808:     {
 809:         if($derPubKey !== null)
 810:         {
 811:             if($compressed === true) {
 812:                 $address = $this->getPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
 813:             }
 814:             else {
 815:                 $address = $this->getUncompressedPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
 816:             }
 817:         }
 818:         else
 819:         {
 820:             if($compressed === true) {
 821:                 $address = $this->getPubKey();
 822:             }
 823:             else {
 824:                 $address = $this->getUncompressedPubKey();
 825:             }
 826:         }
 827: 
 828:         $address = $this->getNetworkPrefix() . $this->hash160(hex2bin($address));
 829: 
 830:         //checksum
 831:         $address = $address.substr($this->hash256(hex2bin($address)), 0, 8);
 832:         $address = $this->base58_encode($address);
 833: 
 834:         if($this->validateAddress($address))
 835:             return $address;
 836:         else
 837:             throw new \Exception('the generated address seems not to be valid.');
 838:     }
 839: 
 840: 
 841: 
 842: 
 843:     /***
 844:      * returns the compressed Bitcoin address generated from the private key.
 845:      *
 846:      * @param string $derPubKey (hexa)
 847:      * @return String (base58)
 848:      */
 849:     public function getAddress($derPubKey = null)
 850:     {
 851:         return $this->getUncompressedAddress(true, $derPubKey);
 852:     }
 853: 
 854: 
 855: 
 856: 
 857:     /***
 858:      * set a private key.
 859:      *
 860:      * @param string $k (hexa)
 861:      * @throws \Exception
 862:      */
 863:     public function setPrivateKey($k)
 864:     {
 865:         //private key has to be passed as an hexadecimal number
 866:         if(gmp_cmp(gmp_init($k, 16), gmp_sub($this->n, gmp_init(1, 10))) === 1)
 867:         {
 868:             throw new \Exception('Private Key is not in the 1,n-1 range');
 869:         }
 870:         $this->k = $k;
 871:     }
 872: 
 873:     /***
 874:      * return the private key.
 875:      *
 876:      * @return string (hexa)
 877:      */
 878:     public function getPrivateKey()
 879:     {
 880:         return $this->k;
 881:     }
 882: 
 883: 
 884: 
 885: 
 886: 
 887:     /***
 888:      * Generate a new random private key.
 889:      * The extra parameter can be some random data typed down by the user or mouse movements to add randomness.
 890:      *
 891:      * @param string $extra
 892:      * @throws \Exception
 893:      */
 894:     public function generateRandomPrivateKey()
 895:     {
 896:         $this->k    = $this->generateRandom256BitsHexaString();
 897:     }
 898: 
 899: 
 900: 
 901: 
 902: 
 903:     /***
 904:      * Tests if the address is valid or not.
 905:      *
 906:      * @param string $address (base58)
 907:      * @return bool
 908:      */
 909:     public function validateAddress($address)
 910:     {
 911:         $address    = hex2bin($this->base58_decode($address));
 912:         if(strlen($address) !== 25)
 913:             return false;
 914:         $checksum   = substr($address, 21, 4);
 915:         $rawAddress = substr($address, 0, 21);
 916: 
 917:         if(substr(hex2bin($this->hash256($rawAddress)), 0, 4) === $checksum)
 918:             return true;
 919:         else
 920:             return false;
 921:     }
 922: 
 923: 
 924: 
 925: 
 926: 
 927:     /***
 928:      * returns the private key under the Wallet Import Format
 929:      *
 930:      * @return string (base58)
 931:      * @throws \Exception
 932:      */
 933:     public function getWif($compressed = true)
 934:     {
 935:         if(!isset($this->k))
 936:         {
 937:             throw new \Exception('No Private Key was defined');
 938:         }
 939: 
 940:         $k          = $this->k;
 941:         
 942:         while(strlen($k) < 64)
 943:             $k = '0' . $k;
 944:         
 945:         $secretKey  =  $this->getPrivatePrefix() . $k;
 946:         
 947:         if($compressed) {
 948:             $secretKey .= '01';
 949:         }
 950:         
 951:         $secretKey .= substr($this->hash256(hex2bin($secretKey)), 0, 8);
 952: 
 953:         return $this->base58_encode($secretKey);
 954:     }
 955:     
 956: 
 957: 
 958: 
 959:     /***
 960:      * returns the private key under the Wallet Import Format for an uncompressed address
 961:      *
 962:      * @return string (base58)
 963:      * @throws \Exception
 964:      */
 965:     public function getUncompressedWif()
 966:     {
 967:         return $this->getWif(false);
 968:     }
 969: 
 970: 
 971: 
 972: 
 973:     /***
 974:      * Tests if the Wif key (Wallet Import Format) is valid or not.
 975:      *
 976:      * @param string $wif (base58)
 977:      * @return bool
 978:      */
 979:     public function validateWifKey($wif)
 980:     {
 981:         $key         = $this->base58_decode($wif, true);
 982:         $length      = strlen($key);
 983:         $checksum    = $this->hash256(hex2bin(substr($key, 0, $length - 8)));
 984:         if(substr($checksum, 0, 8) === substr($key, $length - 8, 8))
 985:             return true;
 986:         else
 987:             return false;
 988:     }
 989: 
 990: 
 991: 
 992:     /**
 993:      * @param string $wif (base58)
 994:      * @return bool
 995:      */
 996:     public function setPrivateKeyWithWif($wif)
 997:     {
 998:         if(!$this->validateWifKey($wif)) {
 999:             throw new \Exception('Invalid WIF');
1000:         }
1001: 
1002:         $key = $this->base58_decode($wif, true);
1003: 
1004:         $this->setPrivateKey(substr($key, 2, 64));
1005:     }
1006: 
1007: 
1008: 
1009: 
1010:     /***
1011:      * Sign a hash with the private key that was set and returns signatures as an array (R,S)
1012:      *
1013:      * @param string $hash (hexa)
1014:      * @param null $nonce
1015:      * @throws \Exception
1016:      * @return Array
1017:      */
1018:     public function getSignatureHashPoints($hash, $nonce = null)
1019:     {
1020:         $n = $this->n;
1021:         $k = $this->k;
1022: 
1023:         if(empty($k))
1024:         {
1025:             throw new \Exception('No Private Key was defined');
1026:         }
1027: 
1028:         if($nonce === null)
1029:         {
1030:             $nonce      = gmp_strval(
1031:                                      gmp_mod(
1032:                                              gmp_init($this->generateRandom256BitsHexaString(), 16),
1033:                                              $n),
1034:                                      16
1035:             );
1036:         }
1037: 
1038:         //first part of the signature (R).
1039: 
1040:         $rPt = $this->mulPoint($nonce, $this->G);
1041:         $R  = gmp_strval($rPt ['x'], 16);
1042: 
1043:         while(strlen($R) < 64)
1044:         {
1045:             $R = '0' . $R;
1046:         }
1047: 
1048:         //second part of the signature (S).
1049:         //S = nonce^-1 (hash + privKey * R) mod p
1050: 
1051: 
1052:         $S = gmp_mod(
1053:             gmp_mul(
1054:                 gmp_invert(
1055:                     gmp_init($nonce, 16),
1056:                     $n
1057:                 ),
1058:                 gmp_add(
1059:                     gmp_init($hash, 16),
1060:                     gmp_mul(
1061:                         gmp_init($k, 16),
1062:                         gmp_init($R, 16)
1063:                     )
1064:                 )
1065:             ),
1066:             $n
1067:         );
1068: 
1069:         //BIP 62, make sure we use the low-s value
1070:         if(gmp_cmp($S, gmp_div($n, 2)) === 1)
1071:         {
1072:             $S = gmp_sub($n, $S);
1073:         }
1074: 
1075:         $S = gmp_strval($S, 16);
1076: 
1077:         if(strlen($S)%2)
1078:         {
1079:             $S = '0' . $S;
1080:         }
1081: 
1082:         if(strlen($R)%2)
1083:         {
1084:             $R = '0' . $R;
1085:         }
1086: 
1087:         return ['R' => $R, 'S' => $S];
1088:     }
1089: 
1090: 
1091: 
1092: 
1093:     /***
1094:      * Sign a hash with the private key that was set and returns a DER encoded signature
1095:      *
1096:      * @param string $hash (hexa)
1097:      * @param null $nonce
1098:      * @return string
1099:      */
1100:     public function signHash($hash, $nonce = null)
1101:     {
1102:         $points = $this->getSignatureHashPoints($hash, $nonce);
1103: 
1104:         $signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S'];
1105:         $signature = '30' . dechex(strlen(hex2bin($signature))) . $signature;
1106: 
1107:         return $signature;
1108:     }
1109: 
1110: 
1111: 
1112:     /***
1113:      * Signature implementation.
1114:      *
1115:      * @param string $message
1116:      * @param bool $onlySignature
1117:      * @param bool $compressed
1118:      * @param null $nonce
1119:      * @return string
1120:      * @throws \Exception
1121:      */
1122:     public function signMessage($message, $onlySignature = false, $compressed = false, $nonce = null)
1123:     {
1124: 
1125:         $hash   = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)). $message);
1126:         $points = $this->getSignatureHashPoints($hash, $nonce);
1127: 
1128:         $R = $points['R'];
1129:         $S = $points['S'];
1130: 
1131:         while(strlen($R) < 64)
1132:             $R = '0' . $R;
1133: 
1134:         while(strlen($S) < 64)
1135:             $S = '0' . $S;
1136: 
1137:         $res = "\n-----BEGIN BITCOIN SIGNED MESSAGE-----\n";
1138:         $res .= $message;
1139:         $res .= "\n-----BEGIN SIGNATURE-----\n";
1140:         if($compressed === true)
1141:             $res .= $this->getAddress() . "\n";
1142:         else
1143:             $res .= $this->getUncompressedAddress() . "\n";
1144: 
1145:         $finalFlag = 0;
1146:         for($i = 0; $i < 4; $i++)
1147:         {
1148:             $flag = 27;
1149:             if($compressed === true)
1150:                 $flag += 4;
1151:             $flag += $i;
1152: 
1153:             $pubKeyPts = $this->getPubKeyPoints();
1154: 
1155:             $recoveredPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1156: 
1157:             if($this->getDerPubKeyWithPubKeyPoints($pubKeyPts, $compressed) === $recoveredPubKey)
1158:             {
1159:                 $finalFlag = $flag;
1160:             }
1161:         }
1162: 
1163:         //echo "Final flag : " . dechex($finalFlag) . "\n";
1164:         if($finalFlag === 0)
1165:         {
1166:             throw new \Exception('Unable to get a valid signature flag.');
1167:         }
1168: 
1169:         $signature = base64_encode(hex2bin(dechex($finalFlag) . $R . $S));
1170: 
1171:         if($onlySignature) {
1172:             return $signature;
1173:         }
1174: 
1175:         $res .= $signature;
1176:         $res .= "\n-----END BITCOIN SIGNED MESSAGE-----";
1177: 
1178:         return $res;
1179:     }
1180: 
1181: 
1182: 
1183: 
1184: 
1185:     /***
1186:      * extract the public key from the signature and using the recovery flag.
1187:      * see http://crypto.stackexchange.com/a/18106/10927
1188:      * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js
1189:      * possible public keys are r−1(sR−zG) and r−1(sR′−zG)
1190:      * Recovery flag rules are :
1191:      * binary number between 28 and 35 inclusive
1192:      * if the flag is > 30 then the address is compressed.
1193:      *
1194:      * @param int $flag
1195:      * @param string $R (hexa)
1196:      * @param string $S (hexa)
1197:      * @param string $hash (hexa)
1198:      * @return array
1199:      */
1200:     public function getPubKeyWithRS($flag, $R, $S, $hash)
1201:     {
1202: 
1203:         $isCompressed = false;
1204: 
1205:         if ($flag < 27 || $flag >= 35)
1206:             return null;
1207: 
1208:         if($flag >= 31) //if address is compressed
1209:         {
1210:             $isCompressed = true;
1211:             $flag -= 4;
1212:         }
1213: 
1214:         $recid = $flag - 27;
1215: 
1216:         //step 1.1
1217:         $x = gmp_add(
1218:                      gmp_init($R, 16),
1219:                      gmp_mul(
1220:                              $this->n,
1221:                              gmp_div_q( //check if j is equal to 0 or to 1.
1222:                                         gmp_init($recid, 10),
1223:                                         gmp_init(2, 10)
1224:                              )
1225:                      )
1226:              );
1227: 
1228:         //step 1.3
1229:         $y = null;
1230:         if($flag % 2 === 1) //check if y is even.
1231:         {
1232:             $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '02');
1233:             if($gmpY !== null)
1234:                 $y = gmp_init($gmpY, 16);
1235:         }
1236:         else
1237:         {
1238:             $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '03');
1239:             if($gmpY !== null)
1240:                 $y = gmp_init($gmpY, 16);
1241:         }
1242: 
1243:         if($y === null)
1244:             return null;
1245: 
1246:         $Rpt = ['x' => $x, 'y' => $y];
1247: 
1248:         //step 1.6.1
1249:         //calculate r^-1 (S*Rpt - eG)
1250: 
1251:         $eG = $this->mulPoint($hash, $this->G);
1252: 
1253:         $eG['y'] = gmp_mod(gmp_neg($eG['y']), $this->p);
1254: 
1255:         $SR = $this->mulPoint($S, $Rpt);
1256: 
1257:         $pubKey = $this->mulPoint(
1258:                             gmp_strval(gmp_invert(gmp_init($R, 16), $this->n), 16),
1259:                             $this->addPoints(
1260:                                              $SR,
1261:                                              $eG
1262:                             )
1263:                   );
1264: 
1265:         $pubKey['x'] = gmp_strval($pubKey['x'], 16);
1266:         $pubKey['y'] = gmp_strval($pubKey['y'], 16);
1267: 
1268:         while(strlen($pubKey['x']) < 64)
1269:             $pubKey['x'] = '0' . $pubKey['x'];
1270: 
1271:         while(strlen($pubKey['y']) < 64)
1272:             $pubKey['y'] = '0' . $pubKey['y'];
1273: 
1274:         $derPubKey = $this->getDerPubKeyWithPubKeyPoints($pubKey, $isCompressed);
1275: 
1276: 
1277:         if($this->checkSignaturePoints($derPubKey, $R, $S, $hash))
1278:             return $derPubKey;
1279:         else
1280:             return null;
1281: 
1282:     }
1283: 
1284: 
1285: 
1286: 
1287: 
1288:     /***
1289:      * Check signature with public key R & S values of the signature and the message hash.
1290:      *
1291:      * @param string $pubKey (hexa)
1292:      * @param string $R (hexa)
1293:      * @param string $S (hexa)
1294:      * @param string $hash (hexa)
1295:      * @return bool
1296:      */
1297:     public function checkSignaturePoints($pubKey, $R, $S, $hash)
1298:     {
1299:         $G = $this->G;
1300: 
1301:         $pubKeyPts = $this->getPubKeyPointsWithDerPubKey($pubKey);
1302: 
1303:         // S^-1* hash * G + S^-1 * R * Qa
1304: 
1305:         // S^-1* hash
1306:         $exp1 =  gmp_strval(
1307:                             gmp_mul(
1308:                                     gmp_invert(
1309:                                                gmp_init($S, 16),
1310:                                                $this->n
1311:                                     ),
1312:                                     gmp_init($hash, 16)
1313:                             ),
1314:                             16
1315:                  );
1316: 
1317:         // S^-1* hash * G
1318:         $exp1Pt = $this->mulPoint($exp1, $G);
1319: 
1320: 
1321:         // S^-1 * R
1322:         $exp2 =  gmp_strval(
1323:                             gmp_mul(
1324:                                     gmp_invert(
1325:                                                gmp_init($S, 16),
1326:                                                 $this->n
1327:                                     ),
1328:                                     gmp_init($R, 16)
1329:                             ),
1330:                             16
1331:                  );
1332:         // S^-1 * R * Qa
1333: 
1334:         $pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16);
1335:         $pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16);
1336: 
1337:         $exp2Pt = $this->mulPoint($exp2,$pubKeyPts);
1338: 
1339:         $resultingPt = $this->addPoints($exp1Pt, $exp2Pt);
1340: 
1341:         $xRes = gmp_strval($resultingPt['x'], 16);
1342: 
1343:         while(strlen($xRes) < 64)
1344:             $xRes = '0' . $xRes;
1345: 
1346:         if(strtoupper($xRes) === strtoupper($R))
1347:             return true;
1348:         else
1349:             return false;
1350:     }
1351: 
1352: 
1353: 
1354: 
1355:     /***
1356:      * checkSignaturePoints wrapper for DER signatures
1357:      *
1358:      * @param string $pubKey (hexa)
1359:      * @param string $signature (hexa)
1360:      * @param string $hash (hexa)
1361:      * @return bool
1362:      */
1363:     public function checkDerSignature($pubKey, $signature, $hash)
1364:     {
1365:         $signature = hex2bin($signature);
1366:         if(bin2hex(substr($signature, 0, 1)) !== '30')
1367:             return false;
1368: 
1369:         $RLength = hexdec(bin2hex(substr($signature, 3, 1)));
1370:         $R = bin2hex(substr($signature, 4, $RLength));
1371: 
1372:         $SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1)));
1373:         $S = bin2hex(substr($signature, $RLength + 6, $SLength));
1374: 
1375:         //echo "\n\nsignature:\n";
1376:         //print_r(bin2hex($signature));
1377: 
1378:         //echo "\n\nR:\n";
1379:         //print_r($R);
1380:         //echo "\n\nS:\n";
1381:         //print_r($S);
1382: 
1383:         return $this->checkSignaturePoints($pubKey, $R, $S, $hash);
1384:     }
1385: 
1386: 
1387: 
1388: 
1389:     /***
1390:      * checks the signature of a bitcoin signed message.
1391:      *
1392:      * @param string $rawMessage
1393:      * @return bool
1394:      */
1395:     public function checkSignatureForRawMessage($rawMessage)
1396:     {
1397:         //recover message.
1398:         preg_match_all("#-----BEGIN BITCOIN SIGNED MESSAGE-----\n(.{0,})\n-----BEGIN SIGNATURE-----\n#USi", $rawMessage, $out);
1399:         $message = $out[1][0];
1400: 
1401:         preg_match_all("#\n-----BEGIN SIGNATURE-----\n(.{0,})\n(.{0,})\n-----END BITCOIN SIGNED MESSAGE-----#USi", $rawMessage, $out);
1402:         $address = $out[1][0];
1403:         $signature = $out[2][0];
1404: 
1405:         return $this->checkSignatureForMessage($address, $signature, $message);
1406:     }
1407: 
1408: 
1409: 
1410: 
1411: 
1412:     /***
1413:      * checks the signature of a bitcoin signed message.
1414:      *
1415:      * @param string $address (base58)
1416:      * @param string $encodedSignature (base64)
1417:      * @param string $message
1418:      * @return bool
1419:      */
1420:     public function checkSignatureForMessage($address, $encodedSignature, $message)
1421:     {
1422:         $hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)) . $message);
1423: 
1424:         //recover flag
1425:         $signature = base64_decode($encodedSignature);
1426: 
1427:         $flag = hexdec(bin2hex(substr($signature, 0, 1)));
1428: 
1429:         $isCompressed = false;
1430:         if($flag >= 31 & $flag < 35) //if address is compressed
1431:         {
1432:             $isCompressed = true;
1433:         }
1434: 
1435:         $R = bin2hex(substr($signature, 1, 32));
1436:         $S = bin2hex(substr($signature, 33, 32));
1437: 
1438:         $derPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1439: 
1440:         if($isCompressed === true)
1441:             $recoveredAddress = $this->getAddress($derPubKey);
1442:         else
1443:             $recoveredAddress = $this->getUncompressedAddress(false, $derPubKey);
1444: 
1445:         if($address === $recoveredAddress)
1446:             return true;
1447:         else
1448:             return false;
1449:     }
1450: }
1451: 
API documentation generated by ApiGen