1+ <?php
2+
3+ namespace AndKom \Bitcoin \Blockchain ;
4+
5+ use Mdanter \Ecc \EccFactory ;
6+
7+ /**
8+ * Class PublicKey
9+ * @package AndKom\Bitcoin\Blockchain
10+ */
11+ class PublicKey
12+ {
13+ const PREFIX_UNCOMPRESSED = "\x04" ;
14+ const PREFIX_COMPRESSED_EVEN = "\x02" ;
15+ const PREFIX_COMPRESSED_ODD = "\x03" ;
16+
17+ const LENGTH_UNCOMPRESSED = 65 ;
18+ const LENGTH_COMPRESSED = 33 ;
19+
20+ /**
21+ * @var \GMP
22+ */
23+ protected $ x ;
24+
25+ /**
26+ * @var \GMP
27+ */
28+ protected $ y ;
29+
30+ /**
31+ * @var bool
32+ */
33+ protected $ wasOdd ;
34+
35+ /**
36+ * PublicKey constructor.
37+ * @param \GMP $x
38+ * @param \GMP|null $y
39+ * @param bool $wasOdd
40+ */
41+ public function __construct (\GMP $ x , \GMP $ y = null , bool $ wasOdd = false )
42+ {
43+ $ this ->x = $ x ;
44+ $ this ->y = $ y ;
45+ $ this ->wasOdd = $ wasOdd ;
46+ }
47+
48+ /**
49+ * @return \GMP
50+ */
51+ public function getX (): \GMP
52+ {
53+ return $ this ->x ;
54+ }
55+
56+ /**
57+ * @return \GMP
58+ * @throws Exception
59+ */
60+ public function getY (): \GMP
61+ {
62+ if ($ this ->isCompressed ()) {
63+ throw new Exception ("Compressed public key doesn't have Y coordinate. " );
64+ }
65+
66+ return $ this ->y ;
67+ }
68+
69+ /**
70+ * @return bool
71+ */
72+ public function wasOdd (): bool
73+ {
74+ return $ this ->wasOdd ;
75+ }
76+
77+ /**
78+ * @return bool
79+ */
80+ public function isCompressed (): bool
81+ {
82+ return is_null ($ this ->y );
83+ }
84+
85+ /**
86+ * @return PublicKey
87+ * @throws Exception
88+ */
89+ public function compress (): self
90+ {
91+ if ($ this ->isCompressed ()) {
92+ throw new Exception ('Public key is already compressed. ' );
93+ }
94+
95+ $ wasOdd = \gmp_cmp (
96+ \gmp_mod ($ this ->y , \gmp_init (2 )),
97+ \gmp_init (0 )
98+ ) !== 0 ;
99+
100+ return new static ($ this ->x , null , $ wasOdd );
101+ }
102+
103+ /**
104+ * @return PublicKey
105+ * @throws Exception
106+ */
107+ public function decompress (): self
108+ {
109+ if (!$ this ->isCompressed ()) {
110+ throw new Exception ('Public key is already decompressed. ' );
111+ }
112+
113+ $ curve = EccFactory::getSecgCurves ()->generator256k1 ()->getCurve ();
114+ $ y = $ curve ->recoverYfromX ($ this ->wasOdd , $ this ->x );
115+
116+ return new static ($ this ->x , $ y );
117+ }
118+
119+ /**
120+ * @return \Mdanter\Ecc\Crypto\Key\PublicKey
121+ * @throws Exception
122+ */
123+ public function getEccPublicKey (): \Mdanter \Ecc \Crypto \Key \PublicKey
124+ {
125+ $ adapter = EccFactory::getAdapter ();
126+ $ generator = EccFactory::getSecgCurves ()->generator256k1 ();
127+ $ curve = $ generator ->getCurve ();
128+
129+ if ($ this ->isCompressed ()) {
130+ $ key = $ this ->decompress ();
131+ } else {
132+ $ key = $ this ;
133+ }
134+
135+ $ point = $ curve ->getPoint ($ key ->getX (), $ this ->getY ());
136+
137+ return new \Mdanter \Ecc \Crypto \Key \PublicKey ($ adapter , $ generator , $ point );
138+ }
139+
140+ /**
141+ * @param string $data
142+ * @return PublicKey
143+ * @throws Exception
144+ */
145+ static public function parse (string $ data ): self
146+ {
147+ $ length = strlen ($ data );
148+
149+ if ($ length == static ::LENGTH_COMPRESSED ) {
150+ $ prefix = substr ($ data , 0 , 1 );
151+
152+ if ($ prefix != static ::PREFIX_COMPRESSED_ODD && $ prefix != static ::PREFIX_COMPRESSED_EVEN ) {
153+ throw new Exception ('Invalid compressed public key prefix. ' );
154+ }
155+
156+ $ x = \gmp_init (bin2hex (substr ($ data , 1 , 32 )), 16 );
157+ $ y = null ;
158+ } elseif ($ length == static ::LENGTH_UNCOMPRESSED ) {
159+ $ prefix = substr ($ data , 0 , 1 );
160+
161+ if ($ prefix != static ::PREFIX_UNCOMPRESSED ) {
162+ throw new Exception ('Invalid uncompressed public key prefix. ' );
163+ }
164+
165+ $ x = \gmp_init (bin2hex (substr ($ data , 1 , 32 )), 16 );
166+ $ y = \gmp_init (bin2hex (substr ($ data , 33 , 32 )), 16 );
167+ } else {
168+ throw new Exception ('Invalid public key size. ' );
169+ }
170+
171+ return new static ($ x , $ y , $ prefix == static ::PREFIX_COMPRESSED_ODD );
172+ }
173+
174+ /**
175+ * @return string
176+ */
177+ public function serialize (): string
178+ {
179+ $ x = hex2bin (\gmp_strval ($ this ->x , 16 ));
180+
181+ if ($ this ->isCompressed ()) {
182+ $ prefix = $ this ->wasOdd ? static ::PREFIX_COMPRESSED_ODD : static ::PREFIX_COMPRESSED_EVEN ;
183+ $ y = '' ;
184+ } else {
185+ $ prefix = static ::PREFIX_UNCOMPRESSED ;
186+ $ y = hex2bin (\gmp_strval ($ this ->y , 16 ));
187+ }
188+
189+ return $ prefix . $ x . $ y ;
190+ }
191+ }
0 commit comments