Failed Conditions
Push — master ( d2ad41...5d048e )
by Florent
21:38 queued 15:06
created

src/Component/Core/Util/Ecc/Curve.php (3 issues)

Severity

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * The MIT License (MIT)
7
 *
8
 * Copyright (c) 2014-2018 Spomky-Labs
9
 *
10
 * This software may be modified and distributed under the terms
11
 * of the MIT license.  See the LICENSE file for details.
12
 */
13
14
namespace Jose\Component\Core\Util\Ecc;
15
16
/**
17
 * @internal
18
 */
19
class Curve
20
{
21
    /**
22
     * Elliptic curve over the field of integers modulo a prime.
23
     *
24
     * @var \GMP
25
     */
26
    private $a;
27
28
    /**
29
     * @var \GMP
30
     */
31
    private $b;
32
33
    /**
34
     * @var \GMP
35
     */
36
    private $prime;
37
38
    /**
39
     * Binary length of keys associated with these curve parameters.
40
     *
41
     * @var int
42
     */
43
    private $size;
44
45
    /**
46
     * @var Point
47
     */
48
    private $generator;
49
50
    /**
51
     * @param int   $size
52
     * @param \GMP  $prime
53
     * @param \GMP  $a
54
     * @param \GMP  $b
55
     * @param Point $generator
56
     */
57
    public function __construct(int $size, \GMP $prime, \GMP $a, \GMP $b, Point $generator)
58
    {
59
        $this->size = $size;
60
        $this->prime = $prime;
61
        $this->a = $a;
62
        $this->b = $b;
63
        $this->generator = $generator;
64
    }
65
66
    /**
67
     * @return \GMP
68
     */
69
    public function getA(): \GMP
70
    {
71
        return $this->a;
72
    }
73
74
    /**
75
     * @return \GMP
76
     */
77
    public function getB(): \GMP
78
    {
79
        return $this->b;
80
    }
81
82
    /**
83
     * @return \GMP
84
     */
85
    public function getPrime(): \GMP
86
    {
87
        return $this->prime;
88
    }
89
90
    /**
91
     * @return int
92
     */
93
    public function getSize(): int
94
    {
95
        return $this->size;
96
    }
97
98
    /**
99
     * @param \GMP      $x
100
     * @param \GMP      $y
101
     * @param \GMP|null $order
102
     *
103
     * @return Point
104
     */
105
    public function getPoint(\GMP $x, \GMP $y, ?\GMP $order = null): Point
106
    {
107
        if (!$this->contains($x, $y)) {
108
            throw new \RuntimeException('Curve '.$this->__toString().' does not contain point ('.Math::toString($x).', '.Math::toString($y).')');
109
        }
110
        $point = Point::create($x, $y, $order);
111
        if (!is_null($order)) {
112
            $mul = $this->mul($point, $order);
113
            if (!$mul->isInfinity()) {
114
                throw new \RuntimeException('SELF * ORDER MUST EQUAL INFINITY. ('.(string) $mul.' found instead)');
115
            }
116
        }
117
118
        return $point;
119
    }
120
121
    /**
122
     * @param \GMP $x
123
     * @param \GMP $y
124
     *
125
     * @return PublicKey
126
     */
127
    public function getPublicKeyFrom(\GMP $x, \GMP $y): PublicKey
128
    {
129
        $zero = gmp_init(0, 10);
130
        if (Math::cmp($x, $zero) < 0 || Math::cmp($this->generator->getOrder(), $x) <= 0 || Math::cmp($y, $zero) < 0 || Math::cmp($this->generator->getOrder(), $y) <= 0) {
131
            throw new \RuntimeException('Generator point has x and y out of range.');
132
        }
133
        $point = $this->getPoint($x, $y);
134
135
        return PublicKey::create($point);
136
    }
137
138
    /**
139
     * @param \GMP $x
140
     * @param \GMP $y
141
     *
142
     * @return bool
143
     */
144
    public function contains(\GMP $x, \GMP $y): bool
145
    {
146
        $eq_zero = Math::equals(
147
            ModularArithmetic::sub(
148
                Math::pow($y, 2),
149
                Math::add(
150
                    Math::add(
151
                        Math::pow($x, 3),
152
                        Math::mul($this->getA(), $x)
153
                    ),
154
                    $this->getB()
155
                ),
156
                $this->getPrime()
157
            ),
158
            gmp_init(0, 10)
159
        );
160
161
        return $eq_zero;
162
    }
163
164
    /**
165
     * @param Point $one
166
     * @param Point $two
167
     *
168
     * @return Point
169
     */
170
    public function add(Point $one, Point $two): Point
171
    {
172
        if ($two->isInfinity()) {
173
            return clone $one;
174
        }
175
176
        if ($one->isInfinity()) {
177
            return clone $two;
178
        }
179
180
        if (Math::equals($two->getX(), $one->getX())) {
181
            if (Math::equals($two->getY(), $one->getY())) {
182
                return $this->getDouble($one);
183
            } else {
184
                return Point::infinity();
185
            }
186
        }
187
188
        $slope = ModularArithmetic::div(
189
            Math::sub($two->getY(), $one->getY()),
190
            Math::sub($two->getX(), $one->getX()),
191
            $this->getPrime()
192
        );
193
194
        $xR = ModularArithmetic::sub(
195
            Math::sub(Math::pow($slope, 2), $one->getX()),
196
            $two->getX(),
197
            $this->getPrime()
198
        );
199
200
        $yR = ModularArithmetic::sub(
201
            Math::mul($slope, Math::sub($one->getX(), $xR)),
202
            $one->getY(),
203
            $this->getPrime()
204
        );
205
206
        return $this->getPoint($xR, $yR, $one->getOrder());
207
    }
208
209
    /**
210
     * @param Point $one
211
     * @param \GMP  $n
212
     *
213
     * @return Point
214
     */
215
    public function mul(Point $one, \GMP $n): Point
216
    {
217
        if ($one->isInfinity()) {
218
            return Point::infinity();
219
        }
220
221
        /** @var \GMP $zero */
222
        $zero = gmp_init(0, 10);
223
        if (Math::cmp($one->getOrder(), $zero) > 0) {
224
            $n = Math::mod($n, $one->getOrder());
225
        }
226
227
        if (Math::equals($n, $zero)) {
228
            return Point::infinity();
229
        }
230
231
        /** @var Point[] $r */
232
        $r = [
233
            Point::infinity(),
234
            clone $one,
235
        ];
236
237
        $k = $this->getSize();
238
        $n = str_pad(Math::baseConvert(Math::toString($n), 10, 2), $k, '0', STR_PAD_LEFT);
239
240
        for ($i = 0; $i < $k; $i++) {
241
            $j = $n[$i];
242
            Point::cswap($r[0], $r[1], $j ^ 1);
0 ignored issues
show
$r[0] is of type object<Jose\Component\Core\Util\Ecc\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
$r[1] is of type object<Jose\Component\Core\Util\Ecc\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
243
            $r[0] = $this->add($r[0], $r[1]);
244
            $r[1] = $this->getDouble($r[1]);
245
            Point::cswap($r[0], $r[1], $j ^ 1);
0 ignored issues
show
$r[1] is of type object<Jose\Component\Core\Util\Ecc\Point>, but the function expects a object<self>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
246
        }
247
248
        $this->validate($r[0]);
249
250
        return $r[0];
251
    }
252
253
    /**
254
     * @param Curve $other
255
     *
256
     * @return int
257
     */
258
    public function cmp(self $other): int
259
    {
260
        $equal = Math::equals($this->getA(), $other->getA());
261
        $equal &= Math::equals($this->getB(), $other->getB());
262
        $equal &= Math::equals($this->getPrime(), $other->getPrime());
263
264
        return $equal ? 0 : 1;
265
    }
266
267
    /**
268
     * @param Curve $other
269
     *
270
     * @return bool
271
     */
272
    public function equals(self $other): bool
273
    {
274
        return 0 === $this->cmp($other);
275
    }
276
277
    /**
278
     * @return string
279
     */
280
    public function __toString(): string
281
    {
282
        return 'curve('.Math::toString($this->getA()).', '.Math::toString($this->getB()).', '.Math::toString($this->getPrime()).')';
283
    }
284
285
    /**
286
     * @param Point $point
287
     */
288
    private function validate(Point $point)
289
    {
290
        if (!$point->isInfinity() && !$this->contains($point->getX(), $point->getY())) {
291
            throw new \RuntimeException('Invalid point');
292
        }
293
    }
294
295
    /**
296
     * @param Point $point
297
     *
298
     * @return Point
299
     */
300
    public function getDouble(Point $point): Point
301
    {
302
        if ($point->isInfinity()) {
303
            return Point::infinity();
304
        }
305
306
        $a = $this->getA();
307
        $threeX2 = Math::mul(gmp_init(3, 10), Math::pow($point->getX(), 2));
308
309
        $tangent = ModularArithmetic::div(
310
            Math::add($threeX2, $a),
311
            Math::mul(gmp_init(2, 10), $point->getY()),
312
            $this->getPrime()
313
        );
314
315
        $x3 = ModularArithmetic::sub(
316
            Math::pow($tangent, 2),
317
            Math::mul(gmp_init(2, 10), $point->getX()),
318
            $this->getPrime()
319
        );
320
321
        $y3 = ModularArithmetic::sub(
322
            Math::mul($tangent, Math::sub($point->getX(), $x3)),
323
            $point->getY(),
324
            $this->getPrime()
325
        );
326
327
        return $this->getPoint($x3, $y3, $point->getOrder());
328
    }
329
330
    /**
331
     * @return PrivateKey
332
     */
333
    public function createPrivateKey(): PrivateKey
334
    {
335
        return PrivateKey::create($this->generate());
336
    }
337
338
    /**
339
     * @param PrivateKey $privateKey
340
     *
341
     * @return PublicKey
342
     */
343
    public function createPublicKey(PrivateKey $privateKey): PublicKey
344
    {
345
        $point = $this->mul($this->generator, $privateKey->getSecret());
346
347
        return PublicKey::create($point);
348
    }
349
350
    /**
351
     * @return \GMP
352
     */
353
    private function generate(): \GMP
354
    {
355
        $max = $this->generator->getOrder();
356
        $numBits = $this->bnNumBits($max);
357
        $numBytes = (int) ceil($numBits / 8);
358
        // Generate an integer of size >= $numBits
359
        $bytes = random_bytes($numBytes);
360
        $value = Math::stringToInt($bytes);
361
        $mask = gmp_sub(gmp_pow(2, $numBits), 1);
362
        $integer = gmp_and($value, $mask);
363
364
        return $integer;
365
    }
366
367
    /**
368
     * Returns the number of bits used to store this number. Non-significant upper bits are not counted.
369
     *
370
     * @param \GMP $x
371
     *
372
     * @return int
373
     *
374
     * @see https://www.openssl.org/docs/crypto/BN_num_bytes.html
375
     */
376
    private function bnNumBits(\GMP $x): int
377
    {
378
        $zero = gmp_init(0, 10);
379
        if (Math::equals($x, $zero)) {
380
            return 0;
381
        }
382
        $log2 = 0;
383
        while (false === Math::equals($x, $zero)) {
384
            $x = Math::rightShift($x, 1);
385
            $log2++;
386
        }
387
388
        return $log2;
389
    }
390
391
    /**
392
     * @return Point
393
     */
394
    public function getGenerator(): Point
395
    {
396
        return $this->generator;
397
    }
398
}
399