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 ( 7356fd...b88596 )
by Jan Moritz
12s
created

BitcoinECDSA::getPrivatePrefix()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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