GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 5aeb24...7356fd )
by Jan Moritz
03:33
created

BitcoinECDSA::getUncompressedWif()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 4
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/**
4
 *
5
 * @author Jan Moritz Lindemann
6
 */
7
8
namespace BitcoinPHP\BitcoinECDSA;
9
10
if (!extension_loaded('gmp')) {
11
    throw new \Exception('GMP extension seems not to be installed');
12
}
13
14
class BitcoinECDSA
15
{
16
17
    public $k;
18
    public $a;
19
    public $b;
20
    public $p;
21
    public $n;
22
    public $G;
23
    public $networkPrefix;
24
25
    public function __construct()
26
    {
27
        $this->a = gmp_init('0', 10);
28
        $this->b = gmp_init('7', 10);
29
        $this->p = gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F', 16);
30
        $this->n = gmp_init('FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141', 16);
31
32
        $this->G = [
33
                    'x' => gmp_init('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
34
                    'y' => gmp_init('32670510020758816978083085130507043184471273380659243275938904335757337482424')
35
                   ];
36
37
        $this->networkPrefix = '00';
38
    }
39
40
    /***
41
     * Convert a number to a compact Int
42
     * taken from https://github.com/scintill/php-bitcoin-signature-routines/blob/master/verifymessage.php
43
     *
44
     * @param int $i
45
     * @return string (bin)
46
     * @throws \Exception
47
     */
48
    public function numToVarIntString($i) {
49
        if ($i < 0xfd) {
50
            return chr($i);
51
        } else if ($i <= 0xffff) {
52
            return pack('Cv', 0xfd, $i);
53
        } else if ($i <= 0xffffffff) {
54
            return pack('CV', 0xfe, $i);
55
        } else {
56
            throw new \Exception('int too large');
57
        }
58
    }
59
60
    /***
61
     * Set the network prefix, '00' = main network, '6f' = test network.
62
     *
63
     * @param string $prefix (hexa)
64
     */
65
    public function setNetworkPrefix($prefix)
66
    {
67
        $this->networkPrefix = $prefix;
68
    }
69
70
    /**
71
     * Returns the current network prefix, '00' = main network, '6f' = test network.
72
     *
73
     * @return string (hexa)
74
     */
75
    public function getNetworkPrefix()
76
    {
77
        return $this->networkPrefix;
78
    }
79
80
    /***
81
     * Permutation table used for Base58 encoding and decoding.
82
     *
83
     * @param string $char
84
     * @param bool $reverse
85
     * @return string|null
86
     */
87
    public function base58_permutation($char, $reverse = false)
88
    {
89
        $table = [
90
                  '1','2','3','4','5','6','7','8','9','A','B','C','D',
91
                  'E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W',
92
                  'X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','m','n','o',
93
                  'p','q','r','s','t','u','v','w','x','y','z'
94
                 ];
95
96
        if($reverse)
97
        {
98
            $reversedTable = [];
99
            foreach($table as $key => $element)
100
            {
101
                $reversedTable[$element] = $key;
102
            }
103
104
            if(isset($reversedTable[$char]))
105
                return $reversedTable[$char];
106
            else
107
                return null;
108
        }
109
110
        if(isset($table[$char]))
111
            return $table[$char];
112
        else
113
            return null;
114
    }
115
116
    /***
117
     * Bitcoin standard 256 bit hash function : double sha256
118
     *
119
     * @param string $data
120
     * @return string (hexa)
121
     */
122
    public function hash256($data)
123
    {
124
        return hash('sha256', hex2bin(hash('sha256', $data)));
125
    }
126
127
    /**
128
     * @param string $data
129
     * @return string (hexa)
130
     */
131
    public function hash160($data)
132
    {
133
        return hash('ripemd160', hex2bin(hash('sha256', $data)));
134
    }
135
136
    /**
137
     * Generates a random 256 bytes hexadecimal encoded string that is smaller than n
138
     *
139
     * @param string $extra
140
     * @return string (hexa)
141
     * @throws \Exception
142
     */
143
    public function generateRandom256BitsHexaString($extra = 'FkejkzqesrfeifH3ioio9hb55sdssdsdfOO:ss')
144
    {
145
        do
146
        {
147
            $bytes = openssl_random_pseudo_bytes(256, $cStrong);
148
            $hex = bin2hex($bytes);
149
            $random = $hex . microtime(true) . $extra;
150
151
            if ($cStrong === false) {
152
                throw new \Exception('Your system is not able to generate strong enough random numbers');
153
            }
154
            $res = $this->hash256($random);
155
156
        } while(gmp_cmp(gmp_init($res, 16), gmp_sub($this->n, gmp_init(1, 10))) === 1); // make sure the generate string is smaller than n
157
158
        return $res;
159
    }
160
161
    /***
162
     * encode a hexadecimal string in Base58.
163
     *
164
     * @param string $data (hexa)
165
     * @param bool $littleEndian
166
     * @return string (base58)
167
     * @throws \Exception
168
     */
169
    public function base58_encode($data, $littleEndian = true)
170
    {
171
        $res = '';
172
        $dataIntVal = gmp_init($data, 16);
173
        while(gmp_cmp($dataIntVal, gmp_init(0, 10)) > 0)
174
        {
175
            $qr = gmp_div_qr($dataIntVal, gmp_init(58, 10));
176
            $dataIntVal = $qr[0];
177
            $reminder = gmp_strval($qr[1]);
178
            if(!$this->base58_permutation($reminder))
179
            {
180
                throw new \Exception('Something went wrong during base58 encoding');
181
            }
182
            $res .= $this->base58_permutation($reminder);
183
        }
184
185
        //get number of leading zeros
186
        $leading = '';
187
        $i = 0;
188
        while(substr($data, $i, 1) === '0')
189
        {
190
            if($i!== 0 && $i%2)
191
            {
192
                $leading .= '1';
193
            }
194
            $i++;
195
        }
196
197
        if($littleEndian)
198
            return strrev($res . $leading);
199
        else
200
            return $res.$leading;
201
    }
202
203
    /***
204
     * Decode a Base58 encoded string and returns it's value as a hexadecimal string
205
     *
206
     * @param string $encodedData (base58)
207
     * @param bool $littleEndian
208
     * @return string (hexa)
209
     */
210
    public function base58_decode($encodedData, $littleEndian = true)
211
    {
212
        $res = gmp_init(0, 10);
213
        $length = strlen($encodedData);
214
        if($littleEndian)
215
        {
216
            $encodedData = strrev($encodedData);
217
        }
218
219
        for($i = $length - 1; $i >= 0; $i--)
220
        {
221
            $res = gmp_add(
222
                           gmp_mul(
223
                                   $res,
224
                                   gmp_init(58, 10)
225
                           ),
226
                           $this->base58_permutation(substr($encodedData, $i, 1), true)
227
                   );
228
        }
229
230
        $res = gmp_strval($res, 16);
231
        $i = $length - 1;
232
        while(substr($encodedData, $i, 1) === '1')
233
        {
234
            $res = '00' . $res;
235
            $i--;
236
        }
237
238
        if(strlen($res)%2 !== 0)
239
        {
240
            $res = '0' . $res;
241
        }
242
243
        return $res;
244
    }
245
246
    /***
247
     * Computes the result of a point addition and returns the resulting point as an Array.
248
     *
249
     * @param Array $pt
250
     * @return Array Point
251
     * @throws \Exception
252
     */
253
    public function doublePoint(Array $pt)
254
    {
255
        $a = $this->a;
256
        $p = $this->p;
257
258
        $gcd = gmp_strval(gmp_gcd(gmp_mod(gmp_mul(gmp_init(2, 10), $pt['y']), $p),$p));
259
        if($gcd !== '1')
260
        {
261
            throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
262
        }
263
264
        // SLOPE = (3 * ptX^2 + a )/( 2*ptY )
265
        // Equals (3 * ptX^2 + a ) * ( 2*ptY )^-1
266
        $slope = gmp_mod(
267
                         gmp_mul(
268
                                 gmp_invert(
269
                                            gmp_mod(
270
                                                    gmp_mul(
271
                                                            gmp_init(2, 10),
272
                                                            $pt['y']
273
                                                    ),
274
                                                    $p
275
                                            ),
276
                                            $p
277
                                 ),
278
                                 gmp_add(
279
                                         gmp_mul(
280
                                                 gmp_init(3, 10),
281
                                                 gmp_pow($pt['x'], 2)
282
                                         ),
283
                                         $a
284
                                 )
285
                         ),
286
                         $p
287
                );
288
289
        // nPtX = slope^2 - 2 * ptX
290
        // Equals slope^2 - ptX - ptX
291
        $nPt = [];
292
        $nPt['x'] = gmp_mod(
293
                            gmp_sub(
294
                                    gmp_sub(
295
                                            gmp_pow($slope, 2),
296
                                            $pt['x']
297
                                    ),
298
                                    $pt['x']
299
                            ),
300
                            $p
301
                    );
302
303
        // nPtY = slope * (ptX - nPtx) - ptY
304
        $nPt['y'] = gmp_mod(
305
                            gmp_sub(
306
                                    gmp_mul(
307
                                            $slope,
308
                                            gmp_sub(
309
                                                    $pt['x'],
310
                                                    $nPt['x']
311
                                            )
312
                                    ),
313
                                    $pt['y']
314
                            ),
315
                            $p
316
                    );
317
318
        return $nPt;
319
    }
320
321
    /***
322
     * Computes the result of a point addition and returns the resulting point as an Array.
323
     *
324
     * @param Array $pt1
325
     * @param Array $pt2
326
     * @return Array Point
327
     * @throws \Exception
328
     */
329
    public function addPoints(Array $pt1, Array $pt2)
330
    {
331
        $p = $this->p;
332
        if(gmp_cmp($pt1['x'], $pt2['x']) === 0  && gmp_cmp($pt1['y'], $pt2['y']) === 0) //if identical
333
        {
334
            return $this->doublePoint($pt1);
335
        }
336
337
        $gcd = gmp_strval(gmp_gcd(gmp_sub($pt1['x'], $pt2['x']), $p));
338
        if($gcd !== '1')
339
        {
340
            throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
341
        }
342
343
        // SLOPE = (pt1Y - pt2Y)/( pt1X - pt2X )
344
        // Equals (pt1Y - pt2Y) * ( pt1X - pt2X )^-1
345
        $slope      = gmp_mod(
346
                              gmp_mul(
347
                                      gmp_sub(
348
                                              $pt1['y'],
349
                                              $pt2['y']
350
                                      ),
351
                                      gmp_invert(
352
                                                 gmp_sub(
353
                                                         $pt1['x'],
354
                                                         $pt2['x']
355
                                                 ),
356
                                                 $p
357
                                      )
358
                              ),
359
                              $p
360
                      );
361
362
        // nPtX = slope^2 - ptX1 - ptX2
363
        $nPt = [];
364
        $nPt['x']   = gmp_mod(
365
                              gmp_sub(
366
                                      gmp_sub(
367
                                              gmp_pow($slope, 2),
368
                                              $pt1['x']
369
                                      ),
370
                                      $pt2['x']
371
                              ),
372
                              $p
373
                      );
374
375
        // nPtX = slope * (ptX1 - nPtX) - ptY1
376
        $nPt['y']   = gmp_mod(
377
                              gmp_sub(
378
                                      gmp_mul(
379
                                              $slope,
380
                                              gmp_sub(
381
                                                      $pt1['x'],
382
                                                      $nPt['x']
383
                                              )
384
                                      ),
385
                                      $pt1['y']
386
                              ),
387
                              $p
388
                      );
389
390
        return $nPt;
391
    }
392
393
    /***
394
     * Computes the result of a point multiplication and returns the resulting point as an Array.
395
     *
396
     * @param string|resource $k (hexa|GMP|Other bases definded in base)
397
     * @param Array $pG
398
     * @param $base
399
     * @throws \Exception
400
     * @return Array Point
401
     */
402
    public function mulPoint($k, Array $pG, $base = null)
403
    {
404
        //in order to calculate k*G
405
        if($base === 16 || $base === null || is_resource($base))
406
            $k = gmp_init($k, 16);
407
        if($base === 10)
408
            $k = gmp_init($k, 10);
409
        $kBin = gmp_strval($k, 2);
410
411
        $lastPoint = $pG;
412
        for($i = 1; $i < strlen($kBin); $i++)
413
        {
414
            if(substr($kBin, $i, 1) === '1')
415
            {
416
                $dPt = $this->doublePoint($lastPoint);
417
                $lastPoint = $this->addPoints($dPt, $pG);
418
            }
419
            else
420
            {
421
                $lastPoint = $this->doublePoint($lastPoint);
422
            }
423
        }
424
        if(!$this->validatePoint(gmp_strval($lastPoint['x'], 16), gmp_strval($lastPoint['y'], 16)))
425
            throw new \Exception('The resulting point is not on the curve.');
426
        return $lastPoint;
427
    }
428
429
    /***
430
     * Calculates the square root of $a mod p and returns the 2 solutions as an array.
431
     *
432
     * @param resource $a (GMP)
433
     * @return array|null
434
     * @throws \Exception
435
     */
436
    public function sqrt($a)
437
    {
438
        $p = $this->p;
439
440
        if(gmp_legendre($a, $p) !== 1)
441
        {
442
            //no result
443
            return null;
444
        }
445
446
        if(gmp_strval(gmp_mod($p, gmp_init(4, 10)), 10) === '3')
447
        {
448
            $sqrt1 = gmp_powm(
449
                            $a,
450
                            gmp_div_q(
451
                                gmp_add($p, gmp_init(1, 10)),
452
                                gmp_init(4, 10)
453
                            ),
454
                            $p
455
                    );
456
            // there are always 2 results for a square root
457
            // In an infinite number field you have -2^2 = 2^2 = 4
458
            // In a finite number field you have a^2 = (p-a)^2
459
            $sqrt2 = gmp_mod(gmp_sub($p, $sqrt1), $p);
460
            return [$sqrt1, $sqrt2];
461
        }
462
        else
463
        {
464
            throw new \Exception('P % 4 != 3 , this isn\'t supported yet.');
465
        }
466
    }
467
468
    /***
469
     * Calculate the Y coordinates for a given X coordinate.
470
     *
471
     * @param string $x (hexa)
472
     * @param null $derEvenOrOddCode
473
     * @return array|null|String
474
     */
475
    public function calculateYWithX($x, $derEvenOrOddCode = null)
476
    {
477
        $a  = $this->a;
478
        $b  = $this->b;
479
        $p  = $this->p;
480
481
        $x  = gmp_init($x, 16);
482
        $y2 = gmp_mod(
483
                      gmp_add(
484
                              gmp_add(
485
                                      gmp_powm($x, gmp_init(3, 10), $p),
486
                                      gmp_mul($a, $x)
487
                              ),
488
                              $b
489
                      ),
490
                      $p
491
              );
492
493
        $y = $this->sqrt($y2);
494
495
        if($y === null) //if there is no result
496
        {
497
            return null;
498
        }
499
500
        if($derEvenOrOddCode === null)
501
        {
502
            return $y;
503
        }
504
505
        else if($derEvenOrOddCode === '02') // even
506
        {
507
            $resY = null;
508 View Code Duplication
            if(gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) === '0')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
509
                $resY = gmp_strval($y[0], 16);
510 View Code Duplication
            if(gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) === '0')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
511
                $resY = gmp_strval($y[1], 16);
512 View Code Duplication
            if($resY !== null)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
513
            {
514
                while(strlen($resY) < 64)
515
                {
516
                    $resY = '0' . $resY;
517
                }
518
            }
519
            return $resY;
520
        }
521
        else if($derEvenOrOddCode === '03') // odd
522
        {
523
            $resY = null;
524 View Code Duplication
            if(gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) === '1')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
525
                $resY = gmp_strval($y[0], 16);
526 View Code Duplication
            if(gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) === '1')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
527
                $resY = gmp_strval($y[1], 16);
528 View Code Duplication
            if($resY !== null)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
529
            {
530
                while(strlen($resY) < 64)
531
                {
532
                    $resY = '0' . $resY;
533
                }
534
            }
535
            return $resY;
536
        }
537
538
        return null;
539
    }
540
541
    /***
542
     * returns the public key coordinates as an array.
543
     *
544
     * @param string $derPubKey (hexa)
545
     * @return array
546
     * @throws \Exception
547
     */
548
    public function getPubKeyPointsWithDerPubKey($derPubKey)
549
    {
550
        if(substr($derPubKey, 0, 2) === '04' && strlen($derPubKey) === 130)
551
        {
552
            //uncompressed der encoded public key
553
            $x = substr($derPubKey, 2, 64);
554
            $y = substr($derPubKey, 66, 64);
555
            return ['x' => $x, 'y' => $y];
556
        }
557
        else if((substr($derPubKey, 0, 2) === '02' || substr($derPubKey, 0, 2) === '03') && strlen($derPubKey) === 66)
558
        {
559
            //compressed der encoded public key
560
            $x = substr($derPubKey, 2, 64);
561
            $y = $this->calculateYWithX($x, substr($derPubKey, 0, 2));
562
            return ['x' => $x, 'y' => $y];
563
        }
564
        else
565
        {
566
            throw new \Exception('Invalid derPubKey format : ' . $derPubKey);
567
        }
568
    }
569
570
571
    /**
572
     * @param array $pubKey (array <x:string, y:string>)
573
     * @param bool $compressed
574
     * @return string
575
     */
576
    public function getDerPubKeyWithPubKeyPoints($pubKey, $compressed = true)
577
    {
578
        if($compressed === false)
579
        {
580
            return '04' . $pubKey['x'] . $pubKey['y'];
581
        }
582 View Code Duplication
        else
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
583
        {
584
            if(gmp_strval(gmp_mod(gmp_init($pubKey['y'], 16), gmp_init(2, 10))) === '0')
585
                $pubKey = '02' . $pubKey['x'];	//if $pubKey['y'] is even
586
            else
587
                $pubKey = '03' . $pubKey['x'];	//if $pubKey['y'] is odd
588
589
            return $pubKey;
590
        }
591
    }
592
593
    /***
594
     * Returns true if the point is on the curve and false if it isn't.
595
     *
596
     * @param string $x (hexa)
597
     * @param string $y (hexa)
598
     * @return bool
599
     */
600
    public function validatePoint($x, $y)
601
    {
602
        $a  = $this->a;
603
        $b  = $this->b;
604
        $p  = $this->p;
605
606
        $x  = gmp_init($x, 16);
607
        $y2 = gmp_mod(
608
                        gmp_add(
609
                            gmp_add(
610
                                gmp_powm($x, gmp_init(3, 10), $p),
611
                                gmp_mul($a, $x)
612
                            ),
613
                            $b
614
                        ),
615
                        $p
616
                    );
617
        $y = gmp_mod(gmp_pow(gmp_init($y, 16), 2), $p);
618
619
        if(gmp_cmp($y2, $y) === 0)
620
            return true;
621
        else
622
            return false;
623
    }
624
625
    /***
626
     * returns the X and Y point coordinates of the public key.
627
     *
628
     * @return Array Point
629
     * @throws \Exception
630
     */
631
    public function getPubKeyPoints()
632
    {
633
        $G = $this->G;
634
        $k = $this->k;
635
636
        if(!isset($this->k))
637
        {
638
            throw new \Exception('No Private Key was defined');
639
        }
640
641
        $pubKey = $this->mulPoint(
642
                                          $k,
643
                                          ['x' => $G['x'], 'y' => $G['y']]
644
                                 );
645
646
        $pubKey['x'] = gmp_strval($pubKey['x'], 16);
647
        $pubKey['y'] = gmp_strval($pubKey['y'], 16);
648
649 View Code Duplication
        while(strlen($pubKey['x']) < 64)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
650
        {
651
            $pubKey['x'] = '0' . $pubKey['x'];
652
        }
653
654 View Code Duplication
        while(strlen($pubKey['y']) < 64)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
655
        {
656
            $pubKey['y'] = '0' . $pubKey['y'];
657
        }
658
659
        return $pubKey;
660
    }
661
662
    /***
663
     * returns the uncompressed DER encoded public key.
664
     *
665
     * @param array $pubKeyPts (array <x:string, y:string>)
666
     * @return string (hexa)
667
     * @throws \Exception
668
     */
669
    public function getUncompressedPubKey(array $pubKeyPts = [])
670
    {
671
        if(empty($pubKeyPts))
672
            $pubKeyPts = $this->getPubKeyPoints();
673
        $uncompressedPubKey	= '04' . $pubKeyPts['x'] . $pubKeyPts['y'];
674
675
        return $uncompressedPubKey;
676
    }
677
678
    /***
679
     * returns the compressed DER encoded public key.
680
     *
681
     * @param array $pubKeyPts (array <x:string, y:string>)
682
     * @return array|string
683
     * @throws \Exception
684
     */
685
    public function getPubKey(array $pubKeyPts = [])
686
    {
687
        if(empty($pubKeyPts))
688
            $pubKeyPts = $this->getPubKeyPoints();
689
690 View Code Duplication
        if(gmp_strval(gmp_mod(gmp_init($pubKeyPts['y'], 16), gmp_init(2, 10))) === '0')
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
691
            $compressedPubKey = '02' . $pubKeyPts['x'];	//if $pubKey['y'] is even
692
        else
693
            $compressedPubKey = '03' . $pubKeyPts['x'];	//if $pubKey['y'] is odd
694
695
        return $compressedPubKey;
696
    }
697
698
    /***
699
     * returns the uncompressed Bitcoin address generated from the private key if $compressed is false and
700
     * the compressed if $compressed is true.
701
     *
702
     * @param bool $compressed
703
     * @param string $derPubKey (hexa)
704
     * @throws \Exception
705
     * @return String Base58
706
     */
707
    public function getUncompressedAddress($compressed = false, $derPubKey = null)
708
    {
709
        if($derPubKey !== null)
710
        {
711
            if($compressed === true) {
712
                $address = $this->getPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
713
            }
714
            else {
715
                $address = $this->getUncompressedPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
716
            }
717
        }
718
        else
719
        {
720
            if($compressed === true) {
721
                $address = $this->getPubKey();
722
            }
723
            else {
724
                $address = $this->getUncompressedPubKey();
725
            }
726
        }
727
728
        $address = $this->getNetworkPrefix() . $this->hash160(hex2bin($address));
729
730
        //checksum
731
        $address = $address.substr($this->hash256(hex2bin($address)), 0, 8);
732
        $address = $this->base58_encode($address);
733
734
        if($this->validateAddress($address))
735
            return $address;
736
        else
737
            throw new \Exception('the generated address seems not to be valid.');
738
    }
739
740
    /***
741
     * returns the compressed Bitcoin address generated from the private key.
742
     *
743
     * @param string $derPubKey (hexa)
744
     * @return String (base58)
745
     */
746
    public function getAddress($derPubKey = null)
747
    {
748
        return $this->getUncompressedAddress(true, $derPubKey);
749
    }
750
751
    /***
752
     * set a private key.
753
     *
754
     * @param string $k (hexa)
755
     * @throws \Exception
756
     */
757
    public function setPrivateKey($k)
758
    {
759
        //private key has to be passed as an hexadecimal number
760
        if(gmp_cmp(gmp_init($k, 16), gmp_sub($this->n, gmp_init(1, 10))) === 1)
761
        {
762
            throw new \Exception('Private Key is not in the 1,n-1 range');
763
        }
764
        $this->k = $k;
765
    }
766
767
    /***
768
     * return the private key.
769
     *
770
     * @return string (hexa)
771
     */
772
    public function getPrivateKey()
773
    {
774
        return $this->k;
775
    }
776
777
778
    /***
779
     * Generate a new random private key.
780
     * The extra parameter can be some random data typed down by the user or mouse movements to add randomness.
781
     *
782
     * @param string $extra
783
     * @throws \Exception
784
     */
785
    public function generateRandomPrivateKey($extra = 'FSQF5356dsdsqdfEFEQ3fq4q6dq4s5d')
786
    {
787
        $this->k    = $this->generateRandom256BitsHexaString($extra);
788
    }
789
790
    /***
791
     * Tests if the address is valid or not.
792
     *
793
     * @param string $address (base58)
794
     * @return bool
795
     */
796
    public function validateAddress($address)
797
    {
798
        $address    = hex2bin($this->base58_decode($address));
799
        if(strlen($address) !== 25)
800
            return false;
801
        $checksum   = substr($address, 21, 4);
802
        $rawAddress = substr($address, 0, 21);
803
804
        if(substr(hex2bin($this->hash256($rawAddress)), 0, 4) === $checksum)
805
            return true;
806
        else
807
            return false;
808
    }
809
810
    /***
811
     * returns the private key under the Wallet Import Format
812
     *
813
     * @return string (base58)
814
     * @throws \Exception
815
     */
816
    public function getWif($compressed = true)
817
    {
818
        if(!isset($this->k))
819
        {
820
            throw new \Exception('No Private Key was defined');
821
        }
822
823
        $k          = $this->k;
824
        
825
        while(strlen($k) < 64)
826
            $k = '0' . $k;
827
        
828
        $secretKey  = '80' . $k;
829
        
830
        if($compressed) {
831
            $secretKey .= '01';
832
        }
833
        
834
        $secretKey .= substr($this->hash256(hex2bin($secretKey)), 0, 8);
835
836
        return $this->base58_encode($secretKey);
837
    }
838
    
839
    /***
840
     * returns the private key under the Wallet Import Format for an uncompressed address
841
     *
842
     * @return string (base58)
843
     * @throws \Exception
844
     */
845
    public function getUncompressedWif()
846
    {
847
        return getWif(false);
848
    }
849
850
    /***
851
     * Tests if the Wif key (Wallet Import Format) is valid or not.
852
     *
853
     * @param string $wif (base58)
854
     * @return bool
855
     */
856
    public function validateWifKey($wif)
857
    {
858
        $key         = $this->base58_decode($wif, true);
859
        $length      = strlen($key);
860
        $checksum    = $this->hash256(hex2bin(substr($key, 0, $length - 8)));
861
        if(substr($checksum, 0, 8) === substr($key, $length - 8, 8))
862
            return true;
863
        else
864
            return false;
865
    }
866
867
    /**
868
     * @param string $wif (base58)
869
     * @return bool
870
     */
871
    public function setPrivateKeyWithWif($wif)
872
    {
873
        if(!$this->validateWifKey($wif)) {
874
            throw new \Exception('Invalid WIF');
875
        }
876
877
        $key = $this->base58_decode($wif, true);
878
879
        $this->setPrivateKey(substr($key, 2, 64));
880
    }
881
882
    /***
883
     * Sign a hash with the private key that was set and returns signatures as an array (R,S)
884
     *
885
     * @param string $hash (hexa)
886
     * @param null $nonce
887
     * @throws \Exception
888
     * @return Array
889
     */
890
    public function getSignatureHashPoints($hash, $nonce = null)
891
    {
892
        $n = $this->n;
893
        $k = $this->k;
894
895
        if(empty($k))
896
        {
897
            throw new \Exception('No Private Key was defined');
898
        }
899
900
        if($nonce === null)
901
        {
902
            $nonce      = gmp_strval(
903
                                     gmp_mod(
904
                                             gmp_init($this->generateRandom256BitsHexaString(), 16),
905
                                             $n),
906
                                     16
907
            );
908
        }
909
910
        //first part of the signature (R).
911
912
        $rPt = $this->mulPoint($nonce, $this->G);
913
        $R	= gmp_strval($rPt ['x'], 16);
914
915
        while(strlen($R) < 64)
916
        {
917
            $R = '0' . $R;
918
        }
919
920
        //second part of the signature (S).
921
        //S = nonce^-1 (hash + privKey * R) mod p
922
923
924
        $S = gmp_mod(
925
            gmp_mul(
926
                gmp_invert(
927
                    gmp_init($nonce, 16),
928
                    $n
929
                ),
930
                gmp_add(
931
                    gmp_init($hash, 16),
932
                    gmp_mul(
933
                        gmp_init($k, 16),
934
                        gmp_init($R, 16)
935
                    )
936
                )
937
            ),
938
            $n
939
        );
940
941
        //BIP 62, make sure we use the low-s value
942
        if(gmp_cmp($S, gmp_div($n, 2)) === 1)
943
        {
944
            $S = gmp_sub($n, $S);
945
        }
946
947
        $S = gmp_strval($S, 16);
948
949
        if(strlen($S)%2)
950
        {
951
            $S = '0' . $S;
952
        }
953
954
        if(strlen($R)%2)
955
        {
956
            $R = '0' . $R;
957
        }
958
959
        return ['R' => $R, 'S' => $S];
960
    }
961
962
    /***
963
     * Sign a hash with the private key that was set and returns a DER encoded signature
964
     *
965
     * @param string $hash (hexa)
966
     * @param null $nonce
967
     * @return string
968
     */
969
    public function signHash($hash, $nonce = null)
970
    {
971
        $points = $this->getSignatureHashPoints($hash, $nonce);
972
973
        $signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S'];
974
        $signature = '30' . dechex(strlen(hex2bin($signature))) . $signature;
975
976
        return $signature;
977
    }
978
979
    /***
980
     * Satoshi client's standard message signature implementation.
981
     *
982
     * @param string $message
983
     * @param bool $onlySignature
984
     * @param bool $compressed
985
     * @param null $nonce
986
     * @return string
987
     * @throws \Exception
988
     */
989
    public function signMessage($message, $onlySignature = false ,$compressed = true, $nonce = null)
990
    {
991
992
        $hash   = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)). $message);
993
        $points = $this->getSignatureHashPoints(
994
                                                $hash,
995
                                                $nonce
996
                   );
997
998
        $R = $points['R'];
999
        $S = $points['S'];
1000
1001
        while(strlen($R) < 64)
1002
            $R = '0' . $R;
1003
1004
        while(strlen($S) < 64)
1005
            $S = '0' . $S;
1006
1007
        $res = "\n-----BEGIN BITCOIN SIGNED MESSAGE-----\n";
1008
        $res .= $message;
1009
        $res .= "\n-----BEGIN SIGNATURE-----\n";
1010
        if($compressed === true)
1011
            $res .= $this->getAddress() . "\n";
1012
        else
1013
            $res .= $this->getUncompressedAddress() . "\n";
1014
1015
        $finalFlag = 0;
1016
        for($i = 0; $i < 4; $i++)
1017
        {
1018
            $flag = 27;
1019
            if($compressed === true)
1020
                $flag += 4;
1021
            $flag += $i;
1022
1023
            $pubKeyPts = $this->getPubKeyPoints();
1024
1025
            $recoveredPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1026
1027
            if($this->getDerPubKeyWithPubKeyPoints($pubKeyPts, $compressed) === $recoveredPubKey)
1028
            {
1029
                $finalFlag = $flag;
1030
            }
1031
        }
1032
1033
        //echo "Final flag : " . dechex($finalFlag) . "\n";
1034
        if($finalFlag === 0)
1035
        {
1036
            throw new \Exception('Unable to get a valid signature flag.');
1037
        }
1038
1039
        $signature = base64_encode(hex2bin(dechex($finalFlag) . $R . $S));
1040
1041
        if($onlySignature) {
1042
            return $signature;
1043
        }
1044
1045
        $res .= $signature;
1046
        $res .= "\n-----END BITCOIN SIGNED MESSAGE-----";
1047
1048
        return $res;
1049
    }
1050
1051
    /***
1052
     * extract the public key from the signature and using the recovery flag.
1053
     * see http://crypto.stackexchange.com/a/18106/10927
1054
     * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js
1055
     * possible public keys are r−1(sR−zG) and r−1(sR′−zG)
1056
     * Recovery flag rules are :
1057
     * binary number between 28 and 35 inclusive
1058
     * if the flag is > 30 then the address is compressed.
1059
     *
1060
     * @param int $flag
1061
     * @param string $R (hexa)
1062
     * @param string $S (hexa)
1063
     * @param string $hash (hexa)
1064
     * @return array
1065
     */
1066
    public function getPubKeyWithRS($flag, $R, $S, $hash)
1067
    {
1068
1069
        $isCompressed = false;
1070
1071
        if ($flag < 27 || $flag >= 35)
1072
            return null;
1073
1074
        if($flag >= 31) //if address is compressed
1075
        {
1076
            $isCompressed = true;
1077
            $flag -= 4;
1078
        }
1079
1080
        $recid = $flag - 27;
1081
1082
        //step 1.1
1083
        $x = gmp_add(
1084
                     gmp_init($R, 16),
1085
                     gmp_mul(
1086
                             $this->n,
1087
                             gmp_div_q( //check if j is equal to 0 or to 1.
1088
                                        gmp_init($recid, 10),
1089
                                        gmp_init(2, 10)
1090
                             )
1091
                     )
1092
             );
1093
1094
        //step 1.3
1095
        $y = null;
1096
        if($flag % 2 === 1) //check if y is even.
1097
        {
1098
            $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '02');
1099
            if($gmpY !== null)
1100
                $y = gmp_init($gmpY, 16);
1101
        }
1102
        else
1103
        {
1104
            $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '03');
1105
            if($gmpY !== null)
1106
                $y = gmp_init($gmpY, 16);
1107
        }
1108
1109
        if($y === null)
1110
            return null;
1111
1112
        $Rpt = ['x' => $x, 'y' => $y];
1113
1114
        //step 1.6.1
1115
        //calculate r^-1 (S*Rpt - eG)
1116
1117
        $eG = $this->mulPoint($hash, $this->G);
1118
1119
        $eG['y'] = gmp_mod(gmp_neg($eG['y']), $this->p);
1120
1121
        $SR = $this->mulPoint($S, $Rpt);
1122
1123
        $pubKey = $this->mulPoint(
1124
                            gmp_strval(gmp_invert(gmp_init($R, 16), $this->n), 16),
1125
                            $this->addPoints(
1126
                                             $SR,
1127
                                             $eG
1128
                            )
1129
                  );
1130
1131
        $pubKey['x'] = gmp_strval($pubKey['x'], 16);
1132
        $pubKey['y'] = gmp_strval($pubKey['y'], 16);
1133
1134 View Code Duplication
        while(strlen($pubKey['x']) < 64)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1135
            $pubKey['x'] = '0' . $pubKey['x'];
1136
1137 View Code Duplication
        while(strlen($pubKey['y']) < 64)
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1138
            $pubKey['y'] = '0' . $pubKey['y'];
1139
1140
        $derPubKey = $this->getDerPubKeyWithPubKeyPoints($pubKey, $isCompressed);
1141
1142
1143
        if($this->checkSignaturePoints($derPubKey, $R, $S, $hash))
1144
            return $derPubKey;
1145
        else
1146
            return null;
1147
1148
    }
1149
1150
    /***
1151
     * Check signature with public key R & S values of the signature and the message hash.
1152
     *
1153
     * @param string $pubKey (hexa)
1154
     * @param string $R (hexa)
1155
     * @param string $S (hexa)
1156
     * @param string $hash (hexa)
1157
     * @return bool
1158
     */
1159
    public function checkSignaturePoints($pubKey, $R, $S, $hash)
1160
    {
1161
        $G = $this->G;
1162
1163
        $pubKeyPts = $this->getPubKeyPointsWithDerPubKey($pubKey);
1164
1165
        // S^-1* hash * G + S^-1 * R * Qa
1166
1167
        // S^-1* hash
1168
        $exp1 =  gmp_strval(
1169
                            gmp_mul(
1170
                                    gmp_invert(
1171
                                               gmp_init($S, 16),
1172
                                               $this->n
1173
                                    ),
1174
                                    gmp_init($hash, 16)
1175
                            ),
1176
                            16
1177
                 );
1178
1179
        // S^-1* hash * G
1180
        $exp1Pt = $this->mulPoint($exp1, $G);
1181
1182
1183
        // S^-1 * R
1184
        $exp2 =  gmp_strval(
1185
                            gmp_mul(
1186
                                    gmp_invert(
1187
                                               gmp_init($S, 16),
1188
                                                $this->n
1189
                                    ),
1190
                                    gmp_init($R, 16)
1191
                            ),
1192
                            16
1193
                 );
1194
        // S^-1 * R * Qa
1195
1196
        $pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16);
1197
        $pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16);
1198
1199
        $exp2Pt = $this->mulPoint($exp2,$pubKeyPts);
1200
1201
        $resultingPt = $this->addPoints($exp1Pt, $exp2Pt);
1202
1203
        $xRes = gmp_strval($resultingPt['x'], 16);
1204
1205
        while(strlen($xRes) < 64)
1206
            $xRes = '0' . $xRes;
1207
1208
        if(strtoupper($xRes) === strtoupper($R))
1209
            return true;
1210
        else
1211
            return false;
1212
    }
1213
1214
    /***
1215
     * checkSignaturePoints wrapper for DER signatures
1216
     *
1217
     * @param string $pubKey (hexa)
1218
     * @param string $signature (hexa)
1219
     * @param string $hash (hexa)
1220
     * @return bool
1221
     */
1222
    public function checkDerSignature($pubKey, $signature, $hash)
1223
    {
1224
        $signature = hex2bin($signature);
1225
        if(bin2hex(substr($signature, 0, 1)) !== '30')
1226
            return false;
1227
1228
        $RLength = hexdec(bin2hex(substr($signature, 3, 1)));
1229
        $R = bin2hex(substr($signature, 4, $RLength));
1230
1231
        $SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1)));
1232
        $S = bin2hex(substr($signature, $RLength + 6, $SLength));
1233
1234
        //echo "\n\nsignature:\n";
1235
        //print_r(bin2hex($signature));
1236
1237
        //echo "\n\nR:\n";
1238
        //print_r($R);
1239
        //echo "\n\nS:\n";
1240
        //print_r($S);
1241
1242
        return $this->checkSignaturePoints($pubKey, $R, $S, $hash);
1243
    }
1244
1245
    /***
1246
     * checks the signature of a bitcoin signed message.
1247
     *
1248
     * @param string $rawMessage
1249
     * @return bool
1250
     */
1251
    public function checkSignatureForRawMessage($rawMessage)
1252
    {
1253
        //recover message.
1254
        preg_match_all("#-----BEGIN BITCOIN SIGNED MESSAGE-----\n(.{0,})\n-----BEGIN SIGNATURE-----\n#USi", $rawMessage, $out);
1255
        $message = $out[1][0];
1256
1257
        preg_match_all("#\n-----BEGIN SIGNATURE-----\n(.{0,})\n(.{0,})\n-----END BITCOIN SIGNED MESSAGE-----#USi", $rawMessage, $out);
1258
        $address = $out[1][0];
1259
        $signature = $out[2][0];
1260
1261
        return $this->checkSignatureForMessage($address, $signature, $message);
1262
    }
1263
1264
    /***
1265
     * checks the signature of a bitcoin signed message.
1266
     *
1267
     * @param string $address (base58)
1268
     * @param string $encodedSignature (base64)
1269
     * @param string $message
1270
     * @return bool
1271
     */
1272
    public function checkSignatureForMessage($address, $encodedSignature, $message)
1273
    {
1274
        $hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)) . $message);
1275
1276
        //recover flag
1277
        $signature = base64_decode($encodedSignature);
1278
1279
        $flag = hexdec(bin2hex(substr($signature, 0, 1)));
1280
1281
        $isCompressed = false;
1282
        if($flag >= 31 & $flag < 35) //if address is compressed
1283
        {
1284
            $isCompressed = true;
1285
        }
1286
1287
        $R = bin2hex(substr($signature, 1, 32));
1288
        $S = bin2hex(substr($signature, 33, 32));
1289
1290
        $derPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1291
1292
        if($isCompressed === true)
1293
            $recoveredAddress = $this->getAddress($derPubKey);
1294
        else
1295
            $recoveredAddress = $this->getUncompressedAddress(false, $derPubKey);
1296
1297
        if($address === $recoveredAddress)
1298
            return true;
1299
        else
1300
            return false;
1301
    }
1302
}
1303