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 ( f60c58...a9880f )
by Jan Moritz
01:52
created

BitcoinECDSA::hash160()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 1
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 = array('x' => gmp_init('55066263022277343669578718895168534326250603453777594175500187360389116729240'),
33
                         'y' => gmp_init('32670510020758816978083085130507043184471273380659243275938904335757337482424'));
34
35
        $this->networkPrefix = '00';
36
    }
37
38
    /***
39
     * Convert a number to a compact Int
40
     * taken from https://github.com/scintill/php-bitcoin-signature-routines/blob/master/verifymessage.php
41
     *
42
     * @param $i
43
     * @return string
44
     * @throws \Exception
45
     */
46
    public function numToVarIntString($i) {
47
        if ($i < 0xfd) {
48
            return chr($i);
49
        } else if ($i <= 0xffff) {
50
            return pack('Cv', 0xfd, $i);
51
        } else if ($i <= 0xffffffff) {
52
            return pack('CV', 0xfe, $i);
53
        } else {
54
            throw new \Exception('int too large');
55
        }
56
    }
57
58
    /***
59
     * Set the network prefix, '00' = main network, '6f' = test network.
60
     *
61
     * @param String Hex $prefix
62
     */
63
    public function setNetworkPrefix($prefix)
64
    {
65
        $this->networkPrefix = $prefix;
66
    }
67
68
    /**
69
     * Returns the current network prefix, '00' = main network, '6f' = test network.
70
     *
71
     * @return String Hex
72
     */
73
    public function getNetworkPrefix()
74
    {
75
        return $this->networkPrefix;
76
    }
77
78
    /***
79
     * Permutation table used for Base58 encoding and decoding.
80
     *
81
     * @param $char
82
     * @param bool $reverse
83
     * @return null
84
     */
85
    public function base58_permutation($char, $reverse = false)
86
    {
87
        $table = array('1','2','3','4','5','6','7','8','9','A','B','C','D',
88
                       'E','F','G','H','J','K','L','M','N','P','Q','R','S','T','U','V','W',
89
                       'X','Y','Z','a','b','c','d','e','f','g','h','i','j','k','m','n','o',
90
                       'p','q','r','s','t','u','v','w','x','y','z'
91
                 );
92
93
        if($reverse)
94
        {
95
            $reversedTable = array();
96
            foreach($table as $key => $element)
97
            {
98
                $reversedTable[$element] = $key;
99
            }
100
101
            if(isset($reversedTable[$char]))
102
                return $reversedTable[$char];
103
            else
104
                return null;
105
        }
106
107
        if(isset($table[$char]))
108
            return $table[$char];
109
        else
110
            return null;
111
    }
112
113
    /***
114
     * Bitcoin standard 256 bit hash function : double sha256
115
     *
116
     * @param $data
117
     * @return string
118
     */
119
    public function hash256($data)
120
    {
121
        return hash('sha256', hex2bin(hash('sha256', $data)));
122
    }
123
124
    /**
125
     * @param $data
126
     * @return string
127
     */
128
    public function hash160($data)
129
    {
130
        return hash('ripemd160', hex2bin(hash('sha256', $data)));
131
    }
132
133
    /**
134
     * @param string $extra
135
     * @return string Hex
136
     * @throws \Exception
137
     */
138
    public function generateRandom256BitsHexaString($extra = 'FkejkzqesrfeifH3ioio9hb55sdssdsdfOO:ss')
139
    {
140
        $bytes      = openssl_random_pseudo_bytes(256, $cStrong);
141
        $hex        = bin2hex($bytes);
142
        $random     = $hex . microtime(true).rand(100000000000, 1000000000000) . $extra;
143
144
        if(!$cStrong)
0 ignored issues
show
Bug Best Practice introduced by
The expression $cStrong of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
145
        {
146
            throw new \Exception('Your system is not able to generate strong enough random numbers');
147
        }
148
149
        return $this->hash256($random);
150
    }
151
152
    /***
153
     * encode a hexadecimal string in Base58.
154
     *
155
     * @param String Hex $data
156
     * @param bool $littleEndian
157
     * @return String Base58
158
     * @throws \Exception
159
     */
160
    public function base58_encode($data, $littleEndian = true)
161
    {
162
        $res = '';
163
        $dataIntVal = gmp_init($data, 16);
164
        while(gmp_cmp($dataIntVal, gmp_init(0, 10)) > 0)
165
        {
166
            $qr = gmp_div_qr($dataIntVal, gmp_init(58, 10));
167
            $dataIntVal = $qr[0];
168
            $reminder = gmp_strval($qr[1]);
169
            if(!$this->base58_permutation($reminder))
170
            {
171
                throw new \Exception('Something went wrong during base58 encoding');
172
            }
173
            $res .= $this->base58_permutation($reminder);
174
        }
175
176
        //get number of leading zeros
177
        $leading = '';
178
        $i=0;
179
        while(substr($data, $i, 1) == '0')
180
        {
181
            if($i!= 0 && $i%2)
182
            {
183
                $leading .= '1';
184
            }
185
            $i++;
186
        }
187
188
        if($littleEndian)
189
            return strrev($res . $leading);
190
        else
191
            return $res.$leading;
192
    }
193
194
    /***
195
     * Decode a Base58 encoded string and returns it's value as a hexadecimal string
196
     *
197
     * @param $encodedData
198
     * @param bool $littleEndian
199
     * @return String Hex
200
     */
201
    public function base58_decode($encodedData, $littleEndian = true)
202
    {
203
        $res = gmp_init(0, 10);
204
        $length = strlen($encodedData);
205
        if($littleEndian)
206
        {
207
            $encodedData = strrev($encodedData);
208
        }
209
210
        for($i = $length - 1; $i >= 0; $i--)
211
        {
212
            $res = gmp_add(
213
                           gmp_mul(
214
                                   $res,
215
                                   gmp_init(58, 10)
216
                           ),
217
                           $this->base58_permutation(substr($encodedData, $i, 1), true)
218
                   );
219
        }
220
221
        $res = gmp_strval($res, 16);
222
        $i = $length - 1;
223
        while(substr($encodedData, $i, 1) == '1')
224
        {
225
            $res = '00' . $res;
226
            $i--;
227
        }
228
229
        if(strlen($res)%2 != 0)
230
        {
231
            $res = '0' . $res;
232
        }
233
234
        return $res;
235
    }
236
237
    /***
238
     * returns the private key under the Wallet Import Format
239
     *
240
     * @return String Base58
241
     * @throws \Exception
242
     */
243
    public function getWif()
244
    {
245
        if(!isset($this->k))
246
        {
247
            throw new \Exception('No Private Key was defined');
248
        }
249
250
        $k              = $this->k;
251
        $secretKey      = '80' . $k;
252
        $secretKey     .= substr($this->hash256(hex2bin($secretKey)), 0, 8);
253
254
        return strrev($this->base58_encode($secretKey));
255
    }
256
257
    /***
258
     * Computes the result of a point addition and returns the resulting point as an Array.
259
     *
260
     * @param Array $pt
261
     * @return Array Point
262
     * @throws \Exception
263
     */
264
    public function doublePoint(Array $pt)
265
    {
266
        $a = $this->a;
267
        $p = $this->p;
268
269
        $gcd = gmp_strval(gmp_gcd(gmp_mod(gmp_mul(gmp_init(2, 10), $pt['y']), $p),$p));
270
        if($gcd != '1')
271
        {
272
            throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
273
        }
274
275
        // SLOPE = (3 * ptX^2 + a )/( 2*ptY )
276
        // Equals (3 * ptX^2 + a ) * ( 2*ptY )^-1
277
        $slope = gmp_mod(
278
                         gmp_mul(
279
                                 gmp_invert(
280
                                            gmp_mod(
281
                                                    gmp_mul(
282
                                                            gmp_init(2, 10),
283
                                                            $pt['y']
284
                                                    ),
285
                                                    $p
286
                                            ),
287
                                            $p
288
                                 ),
289
                                 gmp_add(
290
                                         gmp_mul(
291
                                                 gmp_init(3, 10),
292
                                                 gmp_pow($pt['x'], 2)
293
                                         ),
294
                                         $a
295
                                 )
296
                         ),
297
                         $p
298
                );
299
300
        // nPtX = slope^2 - 2 * ptX
301
        // Equals slope^2 - ptX - ptX
302
        $nPt = array();
303
        $nPt['x'] = gmp_mod(
304
                            gmp_sub(
305
                                    gmp_sub(
306
                                            gmp_pow($slope, 2),
307
                                            $pt['x']
308
                                    ),
309
                                    $pt['x']
310
                            ),
311
                            $p
312
                    );
313
314
        // nPtY = slope * (ptX - nPtx) - ptY
315
        $nPt['y'] = gmp_mod(
316
                            gmp_sub(
317
                                    gmp_mul(
318
                                            $slope,
319
                                            gmp_sub(
320
                                                    $pt['x'],
321
                                                    $nPt['x']
322
                                            )
323
                                    ),
324
                                    $pt['y']
325
                            ),
326
                            $p
327
                    );
328
329
        return $nPt;
330
    }
331
332
    /***
333
     * Computes the result of a point addition and returns the resulting point as an Array.
334
     *
335
     * @param Array $pt1
336
     * @param Array $pt2
337
     * @return Array Point
338
     * @throws \Exception
339
     */
340
    public function addPoints(Array $pt1, Array $pt2)
341
    {
342
        $p = $this->p;
343
        if(gmp_cmp($pt1['x'], $pt2['x']) == 0  && gmp_cmp($pt1['y'], $pt2['y']) == 0) //if identical
344
        {
345
            return $this->doublePoint($pt1);
346
        }
347
348
        $gcd = gmp_strval(gmp_gcd(gmp_sub($pt1['x'], $pt2['x']), $p));
349
        if($gcd != '1')
350
        {
351
            throw new \Exception('This library doesn\'t yet supports point at infinity. See https://github.com/BitcoinPHP/BitcoinECDSA.php/issues/9');
352
        }
353
354
        // SLOPE = (pt1Y - pt2Y)/( pt1X - pt2X )
355
        // Equals (pt1Y - pt2Y) * ( pt1X - pt2X )^-1
356
        $slope      = gmp_mod(
357
                              gmp_mul(
358
                                      gmp_sub(
359
                                              $pt1['y'],
360
                                              $pt2['y']
361
                                      ),
362
                                      gmp_invert(
363
                                                 gmp_sub(
364
                                                         $pt1['x'],
365
                                                         $pt2['x']
366
                                                 ),
367
                                                 $p
368
                                      )
369
                              ),
370
                              $p
371
                      );
372
373
        // nPtX = slope^2 - ptX1 - ptX2
374
        $nPt = array();
375
        $nPt['x']   = gmp_mod(
376
                              gmp_sub(
377
                                      gmp_sub(
378
                                              gmp_pow($slope, 2),
379
                                              $pt1['x']
380
                                      ),
381
                                      $pt2['x']
382
                              ),
383
                              $p
384
                      );
385
386
        // nPtX = slope * (ptX1 - nPtX) - ptY1
387
        $nPt['y']   = gmp_mod(
388
                              gmp_sub(
389
                                      gmp_mul(
390
                                              $slope,
391
                                              gmp_sub(
392
                                                      $pt1['x'],
393
                                                      $nPt['x']
394
                                              )
395
                                      ),
396
                                      $pt1['y']
397
                              ),
398
                              $p
399
                      );
400
401
        return $nPt;
402
    }
403
404
    /***
405
     * Computes the result of a point multiplication and returns the resulting point as an Array.
406
     *
407
     * @param String Hex $k
408
     * @param Array $pG
409
     * @param $base
410
     * @throws \Exception
411
     * @return Array Point
412
     */
413
    public function mulPoint($k, Array $pG, $base = null)
414
    {
415
        //in order to calculate k*G
416
        if($base == 16 || $base == null || is_resource($base))
417
            $k = gmp_init($k, 16);
418
        if($base == 10)
419
            $k = gmp_init($k, 10);
420
        $kBin = gmp_strval($k, 2);
421
422
        $lastPoint = $pG;
423
        for($i = 1; $i < strlen($kBin); $i++)
424
        {
425
            if(substr($kBin, $i, 1) == 1 )
426
            {
427
                $dPt = $this->doublePoint($lastPoint);
428
                $lastPoint = $this->addPoints($dPt, $pG);
429
            }
430
            else
431
            {
432
                $lastPoint = $this->doublePoint($lastPoint);
433
            }
434
        }
435
        if(!$this->validatePoint(gmp_strval($lastPoint['x'], 16), gmp_strval($lastPoint['y'], 16)))
436
            throw new \Exception('The resulting point is not on the curve.');
437
        return $lastPoint;
438
    }
439
440
    /***
441
     * Calculates the square root of $a mod p and returns the 2 solutions as an array.
442
     *
443
     * @param $a
444
     * @return array|null
445
     * @throws \Exception
446
     */
447
    public function sqrt($a)
448
    {
449
        $p = $this->p;
450
451
        if(gmp_legendre($a, $p) != 1)
452
        {
453
            //no result
454
            return null;
455
        }
456
457
        if(gmp_strval(gmp_mod($p, gmp_init(4, 10)), 10) == 3)
458
        {
459
            $sqrt1 = gmp_powm(
460
                            $a,
461
                            gmp_div_q(
462
                                gmp_add($p, gmp_init(1, 10)),
463
                                gmp_init(4, 10)
464
                            ),
465
                            $p
466
                    );
467
            // there are always 2 results for a square root
468
            // In an infinite number field you have -2^2 = 2^2 = 4
469
            // In a finite number field you have a^2 = (p-a)^2
470
            $sqrt2 = gmp_mod(gmp_sub($p, $sqrt1), $p);
471
            return array($sqrt1, $sqrt2);
472
        }
473
        else
474
        {
475
            throw new \Exception('P % 4 != 3 , this isn\'t supported yet.');
476
        }
477
    }
478
479
    /***
480
     * Calculate the Y coordinates for a given X coordinate.
481
     *
482
     * @param $x
483
     * @param null $derEvenOrOddCode
484
     * @return array|null|String
485
     */
486
    public function calculateYWithX($x, $derEvenOrOddCode = null)
487
    {
488
        $a  = $this->a;
489
        $b  = $this->b;
490
        $p  = $this->p;
491
492
        $x  = gmp_init($x, 16);
493
        $y2 = gmp_mod(
494
                      gmp_add(
495
                              gmp_add(
496
                                      gmp_powm($x, gmp_init(3, 10), $p),
497
                                      gmp_mul($a, $x)
498
                              ),
499
                              $b
500
                      ),
501
                      $p
502
              );
503
504
        $y = $this->sqrt($y2);
505
506
        if(!$y) //if there is no result
507
        {
508
            return null;
509
        }
510
511
        if(!$derEvenOrOddCode)
512
        {
513
            return $y;
514
        }
515
516
        else if($derEvenOrOddCode == '02') // even
517
        {
518
            $resY = null;
519 View Code Duplication
            if(false == gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10))
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
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...
520
                $resY = gmp_strval($y[0], 16);
521 View Code Duplication
            if(false == gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10))
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) of type string to the boolean false. If you are specifically checking for an empty string, consider using the more explicit === '' instead.
Loading history...
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...
522
                $resY = gmp_strval($y[1], 16);
523
            if($resY)
0 ignored issues
show
Bug Best Practice introduced by
The expression $resY of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
524
            {
525
                while(strlen($resY) < 64)
526
                {
527
                    $resY = '0' . $resY;
528
                }
529
            }
530
            return $resY;
531
        }
532
        else if($derEvenOrOddCode == '03') // odd
533
        {
534
            $resY = null;
535 View Code Duplication
            if(true == gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10))
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing gmp_strval(gmp_mod($y[0], gmp_init(2, 10)), 10) of type string to the boolean true. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
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...
536
                $resY = gmp_strval($y[0], 16);
537 View Code Duplication
            if(true == gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10))
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing gmp_strval(gmp_mod($y[1], gmp_init(2, 10)), 10) of type string to the boolean true. If you are specifically checking for a non-empty string, consider using the more explicit !== '' instead.
Loading history...
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...
538
                $resY = gmp_strval($y[1], 16);
539
            if($resY)
0 ignored issues
show
Bug Best Practice introduced by
The expression $resY of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
540
            {
541
                while(strlen($resY) < 64)
542
                {
543
                    $resY = '0' . $resY;
544
                }
545
            }
546
            return $resY;
547
        }
548
549
        return null;
550
    }
551
552
    /***
553
     * returns the public key coordinates as an array.
554
     *
555
     * @param $derPubKey
556
     * @return array
557
     * @throws \Exception
558
     */
559
    public function getPubKeyPointsWithDerPubKey($derPubKey)
560
    {
561
        if(substr($derPubKey, 0, 2) == '04' && strlen($derPubKey) == 130)
562
        {
563
            //uncompressed der encoded public key
564
            $x = substr($derPubKey, 2, 64);
565
            $y = substr($derPubKey, 66, 64);
566
            return array('x' => $x, 'y' => $y);
567
        }
568
        else if((substr($derPubKey, 0, 2) == '02' || substr($derPubKey, 0, 2) == '03') && strlen($derPubKey) == 66)
569
        {
570
            //compressed der encoded public key
571
            $x = substr($derPubKey, 2, 64);
572
            $y = $this->calculateYWithX($x, substr($derPubKey, 0, 2));
573
            return array('x' => $x, 'y' => $y);
574
        }
575
        else
576
        {
577
            throw new \Exception('Invalid derPubKey format : ' . $derPubKey);
578
        }
579
    }
580
581
582
    public function getDerPubKeyWithPubKeyPoints($pubKey, $compressed = true)
583
    {
584
        if($compressed == false)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
585
        {
586
            return '04' . $pubKey['x'] . $pubKey['y'];
587
        }
588 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...
589
        {
590
            if(gmp_strval(gmp_mod(gmp_init($pubKey['y'], 16), gmp_init(2, 10))) == 0)
591
                $pubKey  	= '02' . $pubKey['x'];	//if $pubKey['y'] is even
592
            else
593
                $pubKey  	= '03' . $pubKey['x'];	//if $pubKey['y'] is odd
594
595
            return $pubKey;
596
        }
597
    }
598
599
    /***
600
     * Returns true if the point is on the curve and false if it isn't.
601
     *
602
     * @param $x
603
     * @param $y
604
     * @return bool
605
     */
606
    public function validatePoint($x, $y)
607
    {
608
        $a  = $this->a;
609
        $b  = $this->b;
610
        $p  = $this->p;
611
612
        $x  = gmp_init($x, 16);
613
        $y2 = gmp_mod(
614
                        gmp_add(
615
                            gmp_add(
616
                                gmp_powm($x, gmp_init(3, 10), $p),
617
                                gmp_mul($a, $x)
618
                            ),
619
                            $b
620
                        ),
621
                        $p
622
                    );
623
        $y = gmp_mod(gmp_pow(gmp_init($y, 16), 2), $p);
624
625
        if(gmp_cmp($y2, $y) == 0)
626
            return true;
627
        else
628
            return false;
629
    }
630
631
    /***
632
     * returns the X and Y point coordinates of the public key.
633
     *
634
     * @return Array Point
635
     * @throws \Exception
636
     */
637
    public function getPubKeyPoints()
638
    {
639
        $G = $this->G;
640
        $k = $this->k;
641
642
        if(!isset($this->k))
643
        {
644
            throw new \Exception('No Private Key was defined');
645
        }
646
647
        $pubKey 	    = $this->mulPoint($k,
648
                                          array('x' => $G['x'], 'y' => $G['y'])
649
                                 );
650
651
        $pubKey['x']	= gmp_strval($pubKey['x'], 16);
652
        $pubKey['y']	= gmp_strval($pubKey['y'], 16);
653
654 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...
655
        {
656
            $pubKey['x'] = '0' . $pubKey['x'];
657
        }
658
659 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...
660
        {
661
            $pubKey['y'] = '0' . $pubKey['y'];
662
        }
663
664
        return $pubKey;
665
    }
666
667
    /***
668
     * returns the uncompressed DER encoded public key.
669
     *
670
     * @param array $pubKeyPts
671
     * @return string
672
     * @throws \Exception
673
     */
674
    public function getUncompressedPubKey(array $pubKeyPts = array())
675
    {
676
        if(empty($pubKeyPts))
677
            $pubKeyPts = $this->getPubKeyPoints();
678
        $uncompressedPubKey	= '04' . $pubKeyPts['x'] . $pubKeyPts['y'];
679
680
        return $uncompressedPubKey;
681
    }
682
683
    /***
684
     * returns the compressed DER encoded public key.
685
     *
686
     * @param array $pubKeyPts
687
     * @return array|string
688
     * @throws \Exception
689
     */
690
    public function getPubKey(array $pubKeyPts = array())
691
    {
692
        if(empty($pubKeyPts))
693
            $pubKeyPts = $this->getPubKeyPoints();
694
695 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...
696
            $compressedPubKey  	= '02' . $pubKeyPts['x'];	//if $pubKey['y'] is even
697
        else
698
            $compressedPubKey  	= '03' . $pubKeyPts['x'];	//if $pubKey['y'] is odd
699
700
        return $compressedPubKey;
701
    }
702
703
    /***
704
     * returns the uncompressed Bitcoin address generated from the private key if $compressed is false and
705
     * the compressed if $compressed is true.
706
     *
707
     * @param bool $compressed
708
     * @param string $derPubKey
709
     * @throws \Exception
710
     * @return String Base58
711
     */
712
    public function getUncompressedAddress($compressed = false, $derPubKey = null)
713
    {
714
        if(null != $derPubKey)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $derPubKey of type string|null against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
715
        {
716
            if($compressed) {
717
                $address    = $this->getPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
718
            }
719
            else {
720
                $address    = $this->getUncompressedPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
721
            }
722
        }
723
        else
724
        {
725
            if($compressed) {
726
                $address 	= $this->getPubKey();
727
            }
728
            else {
729
                $address 	= $this->getUncompressedPubKey();
730
            }
731
        }
732
733
        $address 	    = $this->getNetworkPrefix() . $this->hash160(hex2bin($address));
734
735
        //checksum
736
        $address 	    = $address.substr($this->hash256(hex2bin($address)), 0, 8);
737
        $address        = $this->base58_encode($address);
738
739
        if($this->validateAddress($address))
740
            return $address;
741
        else
742
            throw new \Exception('the generated address seems not to be valid.');
743
    }
744
745
    /***
746
     * returns the compressed Bitcoin address generated from the private key.
747
     *
748
     * @param string $derPubKey
749
     * @return String Base58
750
     */
751
    public function getAddress($derPubKey = null)
752
    {
753
        return $this->getUncompressedAddress(true, $derPubKey);
754
    }
755
756
    /***
757
     * set a private key.
758
     *
759
     * @param String Hex $k
760
     * @throws \Exception
761
     */
762
    public function setPrivateKey($k)
763
    {
764
        //private key has to be passed as an hexadecimal number
765
        if(gmp_cmp(gmp_init($k, 16), gmp_sub($this->n, gmp_init(1, 10))) == 1)
766
        {
767
            throw new \Exception('Private Key is not in the 1,n-1 range');
768
        }
769
        $this->k = $k;
770
    }
771
772
    /***
773
     * return the private key.
774
     *
775
     * @return String Hex
776
     */
777
    public function getPrivateKey()
778
    {
779
        return $this->k;
780
    }
781
782
783
    /***
784
     * Generate a new random private key.
785
     * The extra parameter can be some random data typed down by the user or mouse movements to add randomness.
786
     *
787
     * @param string $extra
788
     * @throws \Exception
789
     */
790
    public function generateRandomPrivateKey($extra = 'FSQF5356dsdsqdfEFEQ3fq4q6dq4s5d')
791
    {
792
        //private key has to be passed as an hexadecimal number
793
        do { //generate a new random private key until to find one that is valid
794
            $this->k    = $this->generateRandom256BitsHexaString($extra);
795
796
        } while(gmp_cmp(gmp_init($this->k, 16), gmp_sub($this->n, gmp_init(1, 10))) == 1);
797
    }
798
799
    /***
800
     * Tests if the address is valid or not.
801
     *
802
     * @param String Base58 $address
803
     * @return bool
804
     */
805
    public function validateAddress($address)
806
    {
807
        $address    = hex2bin($this->base58_decode($address));
808
        if(strlen($address) != 25)
809
            return false;
810
        $checksum   = substr($address, 21, 4);
811
        $rawAddress = substr($address, 0, 21);
812
813
        if(substr(hex2bin($this->hash256($rawAddress)), 0, 4) == $checksum)
814
            return true;
815
        else
816
            return false;
817
    }
818
819
    /***
820
     * Tests if the Wif key (Wallet Import Format) is valid or not.
821
     *
822
     * @param String Base58 $wif
823
     * @return bool
824
     */
825
    public function validateWifKey($wif)
826
    {
827
        $key            = $this->base58_decode($wif, false);
828
        $length         = strlen($key);
829
        $checksum    = $this->hash256(hex2bin(substr($key, 0, $length - 8)));
830
        if(substr($checksum, 0, 8) == substr($key, $length - 8, 8))
831
            return true;
832
        else
833
            return false;
834
    }
835
836
    /***
837
     * Sign a hash with the private key that was set and returns signatures as an array (R,S)
838
     *
839
     * @param $hash
840
     * @param null $nonce
841
     * @throws \Exception
842
     * @return Array
843
     */
844
    public function getSignatureHashPoints($hash, $nonce = null)
845
    {
846
        $n = $this->n;
847
        $k = $this->k;
848
849
        if(empty($k))
850
        {
851
            throw new \Exception('No Private Key was defined');
852
        }
853
854
        if(null == $nonce)
855
        {
856
            $nonce      = gmp_strval(
857
                                     gmp_mod(
858
                                             gmp_init($this->generateRandom256BitsHexaString(), 16),
859
                                             $n),
860
                                     16
861
            );
862
        }
863
864
        //first part of the signature (R).
865
866
        $rPt = $this->mulPoint($nonce, $this->G);
867
        $R	= gmp_strval($rPt ['x'], 16);
868
869
        while(strlen($R) < 64)
870
        {
871
            $R = '0' . $R;
872
        }
873
874
        //second part of the signature (S).
875
        //S = nonce^-1 (hash + privKey * R) mod p
876
877
        $S = gmp_strval(
878
                        gmp_mod(
879
                                gmp_mul(
880
                                        gmp_invert(
881
                                                   gmp_init($nonce, 16),
882
                                                   $n
883
                                        ),
884
                                        gmp_add(
885
                                                gmp_init($hash, 16),
886
                                                gmp_mul(
887
                                                        gmp_init($k, 16),
888
                                                        gmp_init($R, 16)
889
                                                )
890
                                        )
891
                                ),
892
                                $n
893
                        ),
894
                        16
895
             );
896
897
        if(strlen($S)%2)
898
        {
899
            $S = '0' . $S;
900
        }
901
902
        if(strlen($R)%2)
903
        {
904
            $R = '0' . $R;
905
        }
906
907
        return array('R' => $R, 'S' => $S);
908
    }
909
910
    /***
911
     * Sign a hash with the private key that was set and returns a DER encoded signature
912
     *
913
     * @param $hash
914
     * @param null $nonce
915
     * @return string
916
     */
917
    public function signHash($hash, $nonce = null)
918
    {
919
        $points = $this->getSignatureHashPoints($hash, $nonce);
920
921
        $signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S'];
922
        $signature = '30' . dechex(strlen(hex2bin($signature))) . $signature;
923
924
        return $signature;
925
    }
926
927
    /***
928
     * Satoshi client's standard message signature implementation.
929
     *
930
     * @param $message
931
     * @param bool $compressed
932
     * @param null $nonce
933
     * @return string
934
     * @throws \Exception
935
     */
936
    public function signMessage($message, $compressed = true, $nonce = null)
937
    {
938
939
        $hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)). $message);
940
        $points = $this->getSignatureHashPoints(
941
                                                $hash,
942
                                                $nonce
943
                   );
944
945
        $R = $points['R'];
946
        $S = $points['S'];
947
948
        while(strlen($R) < 64)
949
            $R = '0' . $R;
950
951
        while(strlen($S) < 64)
952
            $S = '0' . $S;
953
954
        $res = "\n-----BEGIN BITCOIN SIGNED MESSAGE-----\n";
955
        $res .= $message;
956
        $res .= "\n-----BEGIN SIGNATURE-----\n";
957
        if(true == $compressed)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
958
            $res .= $this->getAddress() . "\n";
959
        else
960
            $res .= $this->getUncompressedAddress() . "\n";
961
962
        $finalFlag = 0;
963
        for($i = 0; $i < 4; $i++)
964
        {
965
            $flag = 27;
966
            if(true == $compressed)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
967
                $flag += 4;
968
            $flag += $i;
969
970
            $pubKeyPts = $this->getPubKeyPoints();
971
            //echo "\nReal pubKey : \n";
972
            //print_r($pubKeyPts);
973
974
            $recoveredPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
975
            //echo "\nRecovered PubKey : \n";
976
            //print_r($recoveredPubKey);
977
978
            if($this->getDerPubKeyWithPubKeyPoints($pubKeyPts, $compressed) == $recoveredPubKey)
979
            {
980
                $finalFlag = $flag;
981
            }
982
        }
983
984
        //echo "Final flag : " . dechex($finalFlag) . "\n";
985
        if(0 == $finalFlag)
986
        {
987
            throw new \Exception('Unable to get a valid signature flag.');
988
        }
989
990
991
        $res .= base64_encode(hex2bin(dechex($finalFlag) . $R . $S));
992
        $res .= "\n-----END BITCOIN SIGNED MESSAGE-----";
993
994
        return $res;
995
    }
996
997
    /***
998
     * extract the public key from the signature and using the recovery flag.
999
     * see http://crypto.stackexchange.com/a/18106/10927
1000
     * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js
1001
     * possible public keys are r−1(sR−zG) and r−1(sR′−zG)
1002
     * Recovery flag rules are :
1003
     * binary number between 28 and 35 inclusive
1004
     * if the flag is > 30 then the address is compressed.
1005
     *
1006
     * @param $flag
1007
     * @param $R
1008
     * @param $S
1009
     * @param $hash
1010
     * @return array
1011
     */
1012
    public function getPubKeyWithRS($flag, $R, $S, $hash)
1013
    {
1014
1015
        $isCompressed = false;
1016
1017
        if ($flag < 27 || $flag >= 35)
1018
            return false;
1019
1020
        if($flag >= 31) //if address is compressed
1021
        {
1022
            $isCompressed = true;
1023
            $flag -= 4;
1024
        }
1025
1026
        $recid = $flag - 27;
1027
1028
        //step 1.1
1029
        $x = null;
0 ignored issues
show
Unused Code introduced by
$x is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1030
        $x = gmp_add(
1031
                     gmp_init($R, 16),
1032
                     gmp_mul(
1033
                             $this->n,
1034
                             gmp_div_q( //check if j is equal to 0 or to 1.
1035
                                        gmp_init($recid, 10),
1036
                                        gmp_init(2, 10)
1037
                             )
1038
                     )
1039
             );
1040
1041
        //step 1.3
1042
        $y = null;
1043
        if(1 == $flag % 2) //check if y is even.
1044
        {
1045
            $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '02');
1046
            if(null != $gmpY)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $gmpY of type null|resource[]|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1047
                $y = gmp_init($gmpY, 16);
1048
        }
1049
        else
1050
        {
1051
            $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '03');
1052
            if(null != $gmpY)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $gmpY of type null|resource[]|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1053
                $y = gmp_init($gmpY, 16);
1054
        }
1055
1056
        if(null == $y)
1057
            return null;
1058
1059
        $Rpt = array('x' => $x, 'y' => $y);
1060
1061
        //step 1.6.1
1062
        //calculate r^-1 (S*Rpt - eG)
1063
1064
        $eG = $this->mulPoint($hash, $this->G);
1065
1066
        $eG['y'] = gmp_mod(gmp_neg($eG['y']), $this->p);
1067
1068
        $SR = $this->mulPoint($S, $Rpt);
1069
1070
        $pubKey = $this->mulPoint(
1071
                            gmp_strval(gmp_invert(gmp_init($R, 16), $this->n), 16),
1072
                            $this->addPoints(
1073
                                             $SR,
1074
                                             $eG
1075
                            )
1076
                  );
1077
1078
        $pubKey['x'] = gmp_strval($pubKey['x'], 16);
1079
        $pubKey['y'] = gmp_strval($pubKey['y'], 16);
1080
1081 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...
1082
            $pubKey['x'] = '0' . $pubKey['x'];
1083
1084 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...
1085
            $pubKey['y'] = '0' . $pubKey['y'];
1086
1087
        $derPubKey = $this->getDerPubKeyWithPubKeyPoints($pubKey, $isCompressed);
1088
1089
1090
        if($this->checkSignaturePoints($derPubKey, $R, $S, $hash))
1091
            return $derPubKey;
1092
        else
1093
            return false;
1094
1095
    }
1096
1097
    /***
1098
     * Check signature with public key R & S values of the signature and the message hash.
1099
     *
1100
     * @param $pubKey
1101
     * @param $R
1102
     * @param $S
1103
     * @param $hash
1104
     * @return bool
1105
     */
1106
    public function checkSignaturePoints($pubKey, $R, $S, $hash)
1107
    {
1108
        $G = $this->G;
1109
1110
        $pubKeyPts = $this->getPubKeyPointsWithDerPubKey($pubKey);
1111
1112
        // S^-1* hash * G + S^-1 * R * Qa
1113
1114
        // S^-1* hash
1115
        $exp1 =  gmp_strval(
1116
                            gmp_mul(
1117
                                    gmp_invert(
1118
                                               gmp_init($S, 16),
1119
                                               $this->n
1120
                                    ),
1121
                                    gmp_init($hash, 16)
1122
                            ),
1123
                            16
1124
                 );
1125
1126
        // S^-1* hash * G
1127
        $exp1Pt = $this->mulPoint($exp1, $G);
1128
1129
1130
        // S^-1 * R
1131
        $exp2 =  gmp_strval(
1132
                            gmp_mul(
1133
                                    gmp_invert(
1134
                                               gmp_init($S, 16),
1135
                                                $this->n
1136
                                    ),
1137
                                    gmp_init($R, 16)
1138
                            ),
1139
                            16
1140
                 );
1141
        // S^-1 * R * Qa
1142
1143
        $pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16);
1144
        $pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16);
1145
1146
        $exp2Pt = $this->mulPoint($exp2,$pubKeyPts);
1147
1148
        $resultingPt = $this->addPoints($exp1Pt, $exp2Pt);
1149
1150
        $xRes = gmp_strval($resultingPt['x'], 16);
1151
1152
        while(strlen($xRes) < 64)
1153
            $xRes = '0' . $xRes;
1154
1155
        if(strtoupper($xRes) == strtoupper($R))
1156
            return true;
1157
        else
1158
            return false;
1159
    }
1160
1161
    /***
1162
     * checkSignaturePoints wrapper for DER signatures
1163
     *
1164
     * @param $pubKey
1165
     * @param $signature
1166
     * @param $hash
1167
     * @return bool
1168
     */
1169
    public function checkDerSignature($pubKey, $signature, $hash)
1170
    {
1171
        $signature = hex2bin($signature);
1172
        if('30' != bin2hex(substr($signature, 0, 1)))
1173
            return false;
1174
1175
        $RLength = hexdec(bin2hex(substr($signature, 3, 1)));
1176
        $R = bin2hex(substr($signature, 4, $RLength));
1177
1178
        $SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1)));
1179
        $S = bin2hex(substr($signature, $RLength + 6, $SLength));
1180
1181
        //echo "\n\nsignature:\n";
1182
        //print_r(bin2hex($signature));
1183
1184
        //echo "\n\nR:\n";
1185
        //print_r($R);
1186
        //echo "\n\nS:\n";
1187
        //print_r($S);
1188
1189
        return $this->checkSignaturePoints($pubKey, $R, $S, $hash);
1190
    }
1191
1192
    /***
1193
     * checks the signature of a bitcoin signed message.
1194
     *
1195
     * @param $rawMessage
1196
     * @return bool
1197
     */
1198
    public function checkSignatureForRawMessage($rawMessage)
1199
    {
1200
        //recover message.
1201
        preg_match_all("#-----BEGIN BITCOIN SIGNED MESSAGE-----\n(.{0,})\n-----BEGIN SIGNATURE-----\n#USi", $rawMessage, $out);
1202
        $message = $out[1][0];
1203
1204
        preg_match_all("#\n-----BEGIN SIGNATURE-----\n(.{0,})\n(.{0,})\n-----END BITCOIN SIGNED MESSAGE-----#USi", $rawMessage, $out);
1205
        $address = $out[1][0];
1206
        $signature = $out[2][0];
1207
1208
        return $this->checkSignatureForMessage($address, $signature, $message);
1209
    }
1210
1211
    /***
1212
     * checks the signature of a bitcoin signed message.
1213
     *
1214
     * @param $address
1215
     * @param $encodedSignature
1216
     * @param $message
1217
     * @return bool
1218
     */
1219
    public function checkSignatureForMessage($address, $encodedSignature, $message)
1220
    {
1221
        $hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)) . $message);
1222
1223
        //recover flag
1224
        $signature = base64_decode($encodedSignature);
1225
1226
        $flag = hexdec(bin2hex(substr($signature, 0, 1)));
1227
1228
        $isCompressed = false;
1229
        if($flag >= 31 & $flag < 35) //if address is compressed
1230
        {
1231
            $isCompressed = true;
1232
        }
1233
1234
        $R = bin2hex(substr($signature, 1, 32));
1235
        $S = bin2hex(substr($signature, 33, 32));
1236
1237
        $derPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1238
1239
        if($isCompressed == true)
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1240
            $recoveredAddress = $this->getAddress($derPubKey);
0 ignored issues
show
Security Bug introduced by
It seems like $derPubKey defined by $this->getPubKeyWithRS($flag, $R, $S, $hash) on line 1237 can also be of type false; however, BitcoinPHP\BitcoinECDSA\BitcoinECDSA::getAddress() does only seem to accept string|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1241
        else
1242
            $recoveredAddress = $this->getUncompressedAddress(false, $derPubKey);
0 ignored issues
show
Security Bug introduced by
It seems like $derPubKey defined by $this->getPubKeyWithRS($flag, $R, $S, $hash) on line 1237 can also be of type false; however, BitcoinPHP\BitcoinECDSA\...etUncompressedAddress() does only seem to accept string|null, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
1243
1244
        if($address == $recoveredAddress)
1245
            return true;
1246
        else
1247
            return false;
1248
    }
1249
}
1250
1251
?>
0 ignored issues
show
Best Practice introduced by
It is not recommended to use PHP's closing tag ?> in files other than templates.

Using a closing tag in PHP files that only contain PHP code is not recommended as you might accidentally add whitespace after the closing tag which would then be output by PHP. This can cause severe problems, for example headers cannot be sent anymore.

A simple precaution is to leave off the closing tag as it is not required, and it also has no negative effects whatsoever.

Loading history...
1252