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