1: <?php
2:
3: namespace Liberty;
4:
5: use Liberty\BigInteger;
6:
7:
8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 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:
51: $this->networkPrefix = '30';
52: }
53:
54:
55:
56:
57: 58: 59: 60: 61: 62: 63: 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: 82: 83: 84:
85: public function setNetworkPrefix($prefix)
86: {
87: $this->networkPrefix = $prefix;
88: }
89:
90:
91:
92:
93: 94: 95: 96: 97:
98: public function getNetworkPrefix()
99: {
100: return $this->networkPrefix;
101: }
102:
103:
104:
105: 106: 107: 108: 109:
110: public function getPrivatePrefix(){
111: if($this->networkPrefix =='6f')
112: return 'ef';
113: else
114: return 'b0';
115: }
116:
117:
118:
119:
120: 121: 122: 123: 124: 125: 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: 161: 162: 163: 164:
165: public function hash256($data)
166: {
167: return hash('sha256', hex2bin(hash('sha256', $data)));
168: }
169:
170:
171:
172:
173: 174: 175: 176:
177: public function hash160($data)
178: {
179: return hash('ripemd160', hex2bin(hash('sha256', $data)));
180: }
181:
182:
183:
184:
185: 186: 187: 188: 189: 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);
207:
208: return $res;
209: }
210:
211:
212:
213:
214: 215: 216: 217: 218: 219: 220: 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:
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: 262: 263: 264: 265: 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: 304: 305: 306: 307: 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:
325:
326:
327:
328:
329:
330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361: 362: 363: 364: 365: 366: 367: 368: 369: 370: 371: 372: 373: 374: 375: 376: 377: 378: 379: 380: 381: 382: 383: 384: 385:
386: }
387:
388:
389:
390:
391: 392: 393: 394: 395: 396: 397: 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)
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:
414:
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:
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:
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: 468: 469: 470: 471: 472: 473: 474:
475: public function mulPoint($k, Array $pG, $base = null)
476: {
477:
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: 507: 508: 509: 510: 511:
512: public function sqrt($a)
513: {
514: $p = $this->p;
515:
516: if(gmp_legendre($a, $p) !== 1)
517: {
518:
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:
533:
534:
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: 549: 550: 551: 552: 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)
575: {
576: return null;
577: }
578:
579: if($derEvenOrOddCode === null)
580: {
581: return $y;
582: }
583:
584: else if($derEvenOrOddCode === '02')
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')
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: 625: 626: 627: 628: 629:
630: public function getPubKeyPointsWithDerPubKey($derPubKey)
631: {
632: if(substr($derPubKey, 0, 2) === '04' && strlen($derPubKey) === 130)
633: {
634:
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:
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: 657: 658: 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'];
670: else
671: $pubKey = '03' . $pubKey['x'];
672:
673: return $pubKey;
674: }
675: }
676:
677:
678:
679:
680: 681: 682: 683: 684: 685: 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: 718: 719: 720: 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: 758: 759: 760: 761: 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: 777: 778: 779: 780: 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'];
789: else
790: $compressedPubKey = '03' . $pubKeyPts['x'];
791:
792: return $compressedPubKey;
793: }
794:
795:
796:
797:
798: 799: 800: 801: 802: 803: 804: 805: 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:
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: 845: 846: 847: 848:
849: public function getAddress($derPubKey = null)
850: {
851: return $this->getUncompressedAddress(true, $derPubKey);
852: }
853:
854:
855:
856:
857: 858: 859: 860: 861: 862:
863: public function setPrivateKey($k)
864: {
865:
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: 875: 876: 877:
878: public function getPrivateKey()
879: {
880: return $this->k;
881: }
882:
883:
884:
885:
886:
887: 888: 889: 890: 891: 892: 893:
894: public function generateRandomPrivateKey()
895: {
896: $this->k = $this->generateRandom256BitsHexaString();
897: }
898:
899:
900:
901:
902:
903: 904: 905: 906: 907: 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: 929: 930: 931: 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: 961: 962: 963: 964:
965: public function getUncompressedWif()
966: {
967: return $this->getWif(false);
968: }
969:
970:
971:
972:
973: 974: 975: 976: 977: 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: 994: 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: 1012: 1013: 1014: 1015: 1016: 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:
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:
1049:
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:
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: 1095: 1096: 1097: 1098: 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: 1114: 1115: 1116: 1117: 1118: 1119: 1120: 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:
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: 1187: 1188: 1189: 1190: 1191: 1192: 1193: 1194: 1195: 1196: 1197: 1198: 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)
1209: {
1210: $isCompressed = true;
1211: $flag -= 4;
1212: }
1213:
1214: $recid = $flag - 27;
1215:
1216:
1217: $x = gmp_add(
1218: gmp_init($R, 16),
1219: gmp_mul(
1220: $this->n,
1221: gmp_div_q(
1222: gmp_init($recid, 10),
1223: gmp_init(2, 10)
1224: )
1225: )
1226: );
1227:
1228:
1229: $y = null;
1230: if($flag % 2 === 1)
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:
1249:
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: 1290: 1291: 1292: 1293: 1294: 1295: 1296:
1297: public function checkSignaturePoints($pubKey, $R, $S, $hash)
1298: {
1299: $G = $this->G;
1300:
1301: $pubKeyPts = $this->getPubKeyPointsWithDerPubKey($pubKey);
1302:
1303:
1304:
1305:
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:
1318: $exp1Pt = $this->mulPoint($exp1, $G);
1319:
1320:
1321:
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:
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: 1357: 1358: 1359: 1360: 1361: 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:
1376:
1377:
1378:
1379:
1380:
1381:
1382:
1383: return $this->checkSignaturePoints($pubKey, $R, $S, $hash);
1384: }
1385:
1386:
1387:
1388:
1389: 1390: 1391: 1392: 1393: 1394:
1395: public function checkSignatureForRawMessage($rawMessage)
1396: {
1397:
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: 1414: 1415: 1416: 1417: 1418: 1419:
1420: public function checkSignatureForMessage($address, $encodedSignature, $message)
1421: {
1422: $hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)) . $message);
1423:
1424:
1425: $signature = base64_decode($encodedSignature);
1426:
1427: $flag = hexdec(bin2hex(substr($signature, 0, 1)));
1428:
1429: $isCompressed = false;
1430: if($flag >= 31 & $flag < 35)
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: