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 ( 068277...0d83cf )
by Jan Moritz
01:59
created

BitcoinECDSA::setPrivateKeyWithWif()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Loading history...
691
            $compressedPubKey = '02' . $pubKeyPts['x'];	//if $pubKey['y'] is even
692
        else
693
            $compressedPubKey = '03' . $pubKeyPts['x'];	//if $pubKey['y'] is odd
694
695
        return $compressedPubKey;
696
    }
697
698
    /***
699
     * returns the uncompressed Bitcoin address generated from the private key if $compressed is false and
700
     * the compressed if $compressed is true.
701
     *
702
     * @param bool $compressed
703
     * @param string $derPubKey (hexa)
704
     * @throws \Exception
705
     * @return String Base58
706
     */
707
    public function getUncompressedAddress($compressed = false, $derPubKey = null)
708
    {
709
        if($derPubKey !== null)
710
        {
711
            if($compressed === true) {
712
                $address = $this->getPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
713
            }
714
            else {
715
                $address = $this->getUncompressedPubKey($this->getPubKeyPointsWithDerPubKey($derPubKey));
716
            }
717
        }
718
        else
719
        {
720
            if($compressed === true) {
721
                $address = $this->getPubKey();
722
            }
723
            else {
724
                $address = $this->getUncompressedPubKey();
725
            }
726
        }
727
728
        $address = $this->getNetworkPrefix() . $this->hash160(hex2bin($address));
729
730
        //checksum
731
        $address = $address.substr($this->hash256(hex2bin($address)), 0, 8);
732
        $address = $this->base58_encode($address);
733
734
        if($this->validateAddress($address))
735
            return $address;
736
        else
737
            throw new \Exception('the generated address seems not to be valid.');
738
    }
739
740
    /***
741
     * returns the compressed Bitcoin address generated from the private key.
742
     *
743
     * @param string $derPubKey (hexa)
744
     * @return String (base58)
745
     */
746
    public function getAddress($derPubKey = null)
747
    {
748
        return $this->getUncompressedAddress(true, $derPubKey);
749
    }
750
751
    /***
752
     * set a private key.
753
     *
754
     * @param string $k (hexa)
755
     * @throws \Exception
756
     */
757
    public function setPrivateKey($k)
758
    {
759
        //private key has to be passed as an hexadecimal number
760
        if(gmp_cmp(gmp_init($k, 16), gmp_sub($this->n, gmp_init(1, 10))) === 1)
761
        {
762
            throw new \Exception('Private Key is not in the 1,n-1 range');
763
        }
764
        $this->k = $k;
765
    }
766
767
    /***
768
     * return the private key.
769
     *
770
     * @return string (hexa)
771
     */
772
    public function getPrivateKey()
773
    {
774
        return $this->k;
775
    }
776
777
778
    /***
779
     * Generate a new random private key.
780
     * The extra parameter can be some random data typed down by the user or mouse movements to add randomness.
781
     *
782
     * @param string $extra
783
     * @throws \Exception
784
     */
785
    public function generateRandomPrivateKey($extra = 'FSQF5356dsdsqdfEFEQ3fq4q6dq4s5d')
786
    {
787
        $this->k    = $this->generateRandom256BitsHexaString($extra);
788
    }
789
790
    /***
791
     * Tests if the address is valid or not.
792
     *
793
     * @param string $address (base58)
794
     * @return bool
795
     */
796
    public function validateAddress($address)
797
    {
798
        $address    = hex2bin($this->base58_decode($address));
799
        if(strlen($address) !== 25)
800
            return false;
801
        $checksum   = substr($address, 21, 4);
802
        $rawAddress = substr($address, 0, 21);
803
804
        if(substr(hex2bin($this->hash256($rawAddress)), 0, 4) === $checksum)
805
            return true;
806
        else
807
            return false;
808
    }
809
810
    /***
811
     * returns the private key under the Wallet Import Format
812
     *
813
     * @return string (base58)
814
     * @throws \Exception
815
     */
816
    public function getWif()
817
    {
818
        if(!isset($this->k))
819
        {
820
            throw new \Exception('No Private Key was defined');
821
        }
822
823
        $k          = $this->k;
824
        $secretKey  = '80' . $k;
825
        $secretKey .= substr($this->hash256(hex2bin($secretKey)), 0, 8);
826
827
        return $this->base58_encode($secretKey);
828
    }
829
830
    /***
831
     * Tests if the Wif key (Wallet Import Format) is valid or not.
832
     *
833
     * @param string $wif (base58)
834
     * @return bool
835
     */
836
    public function validateWifKey($wif)
837
    {
838
        $key         = $this->base58_decode($wif, true);
839
        $length      = strlen($key);
840
        $checksum    = $this->hash256(hex2bin(substr($key, 0, $length - 8)));
841
        if(substr($checksum, 0, 8) === substr($key, $length - 8, 8))
842
            return true;
843
        else
844
            return false;
845
    }
846
847
    /**
848
     * @param string $wif (base58)
849
     * @return bool
850
     */
851
    public function setPrivateKeyWithWif($wif)
852
    {
853
        if(!$this->validateWifKey($wif)) {
854
            throw new \Exception('Invalid WIF');
855
        }
856
857
        $key = $this->base58_decode($wif, true);
858
        $this->setPrivateKey(substr($key, 1, strlen($key) - 9));
859
    }
860
861
    /***
862
     * Sign a hash with the private key that was set and returns signatures as an array (R,S)
863
     *
864
     * @param string $hash (hexa)
865
     * @param null $nonce
866
     * @throws \Exception
867
     * @return Array
868
     */
869
    public function getSignatureHashPoints($hash, $nonce = null)
870
    {
871
        $n = $this->n;
872
        $k = $this->k;
873
874
        if(empty($k))
875
        {
876
            throw new \Exception('No Private Key was defined');
877
        }
878
879
        if($nonce === null)
880
        {
881
            $nonce      = gmp_strval(
882
                                     gmp_mod(
883
                                             gmp_init($this->generateRandom256BitsHexaString(), 16),
884
                                             $n),
885
                                     16
886
            );
887
        }
888
889
        //first part of the signature (R).
890
891
        $rPt = $this->mulPoint($nonce, $this->G);
892
        $R	= gmp_strval($rPt ['x'], 16);
893
894
        while(strlen($R) < 64)
895
        {
896
            $R = '0' . $R;
897
        }
898
899
        //second part of the signature (S).
900
        //S = nonce^-1 (hash + privKey * R) mod p
901
902
903
        $S = gmp_mod(
904
            gmp_mul(
905
                gmp_invert(
906
                    gmp_init($nonce, 16),
907
                    $n
908
                ),
909
                gmp_add(
910
                    gmp_init($hash, 16),
911
                    gmp_mul(
912
                        gmp_init($k, 16),
913
                        gmp_init($R, 16)
914
                    )
915
                )
916
            ),
917
            $n
918
        );
919
920
        //BIP 62, make sure we use the low-s value
921
        if(gmp_cmp($S, gmp_div($n, 2)) === 1)
922
        {
923
            $S = gmp_sub($n, $S);
924
        }
925
926
        $S = gmp_strval($S, 16);
927
928
        if(strlen($S)%2)
929
        {
930
            $S = '0' . $S;
931
        }
932
933
        if(strlen($R)%2)
934
        {
935
            $R = '0' . $R;
936
        }
937
938
        return ['R' => $R, 'S' => $S];
939
    }
940
941
    /***
942
     * Sign a hash with the private key that was set and returns a DER encoded signature
943
     *
944
     * @param string $hash (hexa)
945
     * @param null $nonce
946
     * @return string
947
     */
948
    public function signHash($hash, $nonce = null)
949
    {
950
        $points = $this->getSignatureHashPoints($hash, $nonce);
951
952
        $signature = '02' . dechex(strlen(hex2bin($points['R']))) . $points['R'] . '02' . dechex(strlen(hex2bin($points['S']))) . $points['S'];
953
        $signature = '30' . dechex(strlen(hex2bin($signature))) . $signature;
954
955
        return $signature;
956
    }
957
958
    /***
959
     * Satoshi client's standard message signature implementation.
960
     *
961
     * @param string $message
962
     * @param bool $onlySignature
963
     * @param bool $compressed
964
     * @param null $nonce
965
     * @return string
966
     * @throws \Exception
967
     */
968
    public function signMessage($message, $onlySignature = false ,$compressed = true, $nonce = null)
969
    {
970
971
        $hash   = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)). $message);
972
        $points = $this->getSignatureHashPoints(
973
                                                $hash,
974
                                                $nonce
975
                   );
976
977
        $R = $points['R'];
978
        $S = $points['S'];
979
980
        while(strlen($R) < 64)
981
            $R = '0' . $R;
982
983
        while(strlen($S) < 64)
984
            $S = '0' . $S;
985
986
        $res = "\n-----BEGIN BITCOIN SIGNED MESSAGE-----\n";
987
        $res .= $message;
988
        $res .= "\n-----BEGIN SIGNATURE-----\n";
989
        if($compressed === true)
990
            $res .= $this->getAddress() . "\n";
991
        else
992
            $res .= $this->getUncompressedAddress() . "\n";
993
994
        $finalFlag = 0;
995
        for($i = 0; $i < 4; $i++)
996
        {
997
            $flag = 27;
998
            if($compressed === true)
999
                $flag += 4;
1000
            $flag += $i;
1001
1002
            $pubKeyPts = $this->getPubKeyPoints();
1003
1004
            $recoveredPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1005
1006
            if($this->getDerPubKeyWithPubKeyPoints($pubKeyPts, $compressed) === $recoveredPubKey)
1007
            {
1008
                $finalFlag = $flag;
1009
            }
1010
        }
1011
1012
        //echo "Final flag : " . dechex($finalFlag) . "\n";
1013
        if($finalFlag === 0)
1014
        {
1015
            throw new \Exception('Unable to get a valid signature flag.');
1016
        }
1017
1018
        $signature = base64_encode(hex2bin(dechex($finalFlag) . $R . $S));
1019
1020
        if($onlySignature) {
1021
            return $signature;
1022
        }
1023
1024
        $res .= $signature;
1025
        $res .= "\n-----END BITCOIN SIGNED MESSAGE-----";
1026
1027
        return $res;
1028
    }
1029
1030
    /***
1031
     * extract the public key from the signature and using the recovery flag.
1032
     * see http://crypto.stackexchange.com/a/18106/10927
1033
     * based on https://github.com/brainwallet/brainwallet.github.io/blob/master/js/bitcoinsig.js
1034
     * possible public keys are r−1(sR−zG) and r−1(sR′−zG)
1035
     * Recovery flag rules are :
1036
     * binary number between 28 and 35 inclusive
1037
     * if the flag is > 30 then the address is compressed.
1038
     *
1039
     * @param int $flag
1040
     * @param string $R (hexa)
1041
     * @param string $S (hexa)
1042
     * @param string $hash (hexa)
1043
     * @return array
1044
     */
1045
    public function getPubKeyWithRS($flag, $R, $S, $hash)
1046
    {
1047
1048
        $isCompressed = false;
1049
1050
        if ($flag < 27 || $flag >= 35)
1051
            return null;
1052
1053
        if($flag >= 31) //if address is compressed
1054
        {
1055
            $isCompressed = true;
1056
            $flag -= 4;
1057
        }
1058
1059
        $recid = $flag - 27;
1060
1061
        //step 1.1
1062
        $x = gmp_add(
1063
                     gmp_init($R, 16),
1064
                     gmp_mul(
1065
                             $this->n,
1066
                             gmp_div_q( //check if j is equal to 0 or to 1.
1067
                                        gmp_init($recid, 10),
1068
                                        gmp_init(2, 10)
1069
                             )
1070
                     )
1071
             );
1072
1073
        //step 1.3
1074
        $y = null;
1075
        if($flag % 2 === 1) //check if y is even.
1076
        {
1077
            $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '02');
1078
            if($gmpY !== null)
1079
                $y = gmp_init($gmpY, 16);
1080
        }
1081
        else
1082
        {
1083
            $gmpY = $this->calculateYWithX(gmp_strval($x, 16), '03');
1084
            if($gmpY !== null)
1085
                $y = gmp_init($gmpY, 16);
1086
        }
1087
1088
        if($y === null)
1089
            return null;
1090
1091
        $Rpt = ['x' => $x, 'y' => $y];
1092
1093
        //step 1.6.1
1094
        //calculate r^-1 (S*Rpt - eG)
1095
1096
        $eG = $this->mulPoint($hash, $this->G);
1097
1098
        $eG['y'] = gmp_mod(gmp_neg($eG['y']), $this->p);
1099
1100
        $SR = $this->mulPoint($S, $Rpt);
1101
1102
        $pubKey = $this->mulPoint(
1103
                            gmp_strval(gmp_invert(gmp_init($R, 16), $this->n), 16),
1104
                            $this->addPoints(
1105
                                             $SR,
1106
                                             $eG
1107
                            )
1108
                  );
1109
1110
        $pubKey['x'] = gmp_strval($pubKey['x'], 16);
1111
        $pubKey['y'] = gmp_strval($pubKey['y'], 16);
1112
1113 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...
1114
            $pubKey['x'] = '0' . $pubKey['x'];
1115
1116 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...
1117
            $pubKey['y'] = '0' . $pubKey['y'];
1118
1119
        $derPubKey = $this->getDerPubKeyWithPubKeyPoints($pubKey, $isCompressed);
1120
1121
1122
        if($this->checkSignaturePoints($derPubKey, $R, $S, $hash))
1123
            return $derPubKey;
1124
        else
1125
            return null;
1126
1127
    }
1128
1129
    /***
1130
     * Check signature with public key R & S values of the signature and the message hash.
1131
     *
1132
     * @param string $pubKey (hexa)
1133
     * @param string $R (hexa)
1134
     * @param string $S (hexa)
1135
     * @param string $hash (hexa)
1136
     * @return bool
1137
     */
1138
    public function checkSignaturePoints($pubKey, $R, $S, $hash)
1139
    {
1140
        $G = $this->G;
1141
1142
        $pubKeyPts = $this->getPubKeyPointsWithDerPubKey($pubKey);
1143
1144
        // S^-1* hash * G + S^-1 * R * Qa
1145
1146
        // S^-1* hash
1147
        $exp1 =  gmp_strval(
1148
                            gmp_mul(
1149
                                    gmp_invert(
1150
                                               gmp_init($S, 16),
1151
                                               $this->n
1152
                                    ),
1153
                                    gmp_init($hash, 16)
1154
                            ),
1155
                            16
1156
                 );
1157
1158
        // S^-1* hash * G
1159
        $exp1Pt = $this->mulPoint($exp1, $G);
1160
1161
1162
        // S^-1 * R
1163
        $exp2 =  gmp_strval(
1164
                            gmp_mul(
1165
                                    gmp_invert(
1166
                                               gmp_init($S, 16),
1167
                                                $this->n
1168
                                    ),
1169
                                    gmp_init($R, 16)
1170
                            ),
1171
                            16
1172
                 );
1173
        // S^-1 * R * Qa
1174
1175
        $pubKeyPts['x'] = gmp_init($pubKeyPts['x'], 16);
1176
        $pubKeyPts['y'] = gmp_init($pubKeyPts['y'], 16);
1177
1178
        $exp2Pt = $this->mulPoint($exp2,$pubKeyPts);
1179
1180
        $resultingPt = $this->addPoints($exp1Pt, $exp2Pt);
1181
1182
        $xRes = gmp_strval($resultingPt['x'], 16);
1183
1184
        while(strlen($xRes) < 64)
1185
            $xRes = '0' . $xRes;
1186
1187
        if(strtoupper($xRes) === strtoupper($R))
1188
            return true;
1189
        else
1190
            return false;
1191
    }
1192
1193
    /***
1194
     * checkSignaturePoints wrapper for DER signatures
1195
     *
1196
     * @param string $pubKey (hexa)
1197
     * @param string $signature (hexa)
1198
     * @param string $hash (hexa)
1199
     * @return bool
1200
     */
1201
    public function checkDerSignature($pubKey, $signature, $hash)
1202
    {
1203
        $signature = hex2bin($signature);
1204
        if(bin2hex(substr($signature, 0, 1)) !== '30')
1205
            return false;
1206
1207
        $RLength = hexdec(bin2hex(substr($signature, 3, 1)));
1208
        $R = bin2hex(substr($signature, 4, $RLength));
1209
1210
        $SLength = hexdec(bin2hex(substr($signature, $RLength + 5, 1)));
1211
        $S = bin2hex(substr($signature, $RLength + 6, $SLength));
1212
1213
        //echo "\n\nsignature:\n";
1214
        //print_r(bin2hex($signature));
1215
1216
        //echo "\n\nR:\n";
1217
        //print_r($R);
1218
        //echo "\n\nS:\n";
1219
        //print_r($S);
1220
1221
        return $this->checkSignaturePoints($pubKey, $R, $S, $hash);
1222
    }
1223
1224
    /***
1225
     * checks the signature of a bitcoin signed message.
1226
     *
1227
     * @param string $rawMessage
1228
     * @return bool
1229
     */
1230
    public function checkSignatureForRawMessage($rawMessage)
1231
    {
1232
        //recover message.
1233
        preg_match_all("#-----BEGIN BITCOIN SIGNED MESSAGE-----\n(.{0,})\n-----BEGIN SIGNATURE-----\n#USi", $rawMessage, $out);
1234
        $message = $out[1][0];
1235
1236
        preg_match_all("#\n-----BEGIN SIGNATURE-----\n(.{0,})\n(.{0,})\n-----END BITCOIN SIGNED MESSAGE-----#USi", $rawMessage, $out);
1237
        $address = $out[1][0];
1238
        $signature = $out[2][0];
1239
1240
        return $this->checkSignatureForMessage($address, $signature, $message);
1241
    }
1242
1243
    /***
1244
     * checks the signature of a bitcoin signed message.
1245
     *
1246
     * @param string $address (base58)
1247
     * @param string $encodedSignature (base64)
1248
     * @param string $message
1249
     * @return bool
1250
     */
1251
    public function checkSignatureForMessage($address, $encodedSignature, $message)
1252
    {
1253
        $hash = $this->hash256("\x18Bitcoin Signed Message:\n" . $this->numToVarIntString(strlen($message)) . $message);
1254
1255
        //recover flag
1256
        $signature = base64_decode($encodedSignature);
1257
1258
        $flag = hexdec(bin2hex(substr($signature, 0, 1)));
1259
1260
        $isCompressed = false;
1261
        if($flag >= 31 & $flag < 35) //if address is compressed
1262
        {
1263
            $isCompressed = true;
1264
        }
1265
1266
        $R = bin2hex(substr($signature, 1, 32));
1267
        $S = bin2hex(substr($signature, 33, 32));
1268
1269
        $derPubKey = $this->getPubKeyWithRS($flag, $R, $S, $hash);
1270
1271
        if($isCompressed === true)
1272
            $recoveredAddress = $this->getAddress($derPubKey);
1273
        else
1274
            $recoveredAddress = $this->getUncompressedAddress(false, $derPubKey);
1275
1276
        if($address === $recoveredAddress)
1277
            return true;
1278
        else
1279
            return false;
1280
    }
1281
}
1282