Completed
Push — master ( a22894...37130c )
by thomas
62:03 queued 58:09
created

EcAdapter::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 2
dl 0
loc 5
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Adapter;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Signature\CompactSignatureInterface;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Signature\SignatureInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Signature\Signature;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
12
use BitWasp\Bitcoin\Crypto\Random\RbgInterface;
13
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
14
use BitWasp\Bitcoin\Math\Math;
15
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PrivateKey;
16
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Signature\CompactSignature;
17
use BitWasp\Buffertools\BufferInterface;
18
use Mdanter\Ecc\Primitives\GeneratorPoint;
19
use Mdanter\Ecc\Primitives\PointInterface;
20
21
class EcAdapter implements EcAdapterInterface
22
{
23
    /**
24
     * @var Math
25
     */
26
    private $math;
27
28
    /**
29
     * @var GeneratorPoint
30
     */
31
    private $generator;
32
33
    /**
34
     * @param Math $math
35
     * @param GeneratorPoint $generator
36
     */
37 26
    public function __construct(Math $math, GeneratorPoint $generator)
38
    {
39 26
        $this->math = $math;
40 26
        $this->generator = $generator;
41 26
    }
42
43
    /**
44
     * @return Math
45
     */
46 640
    public function getMath()
47
    {
48 640
        return $this->math;
49
    }
50
51
    /**
52
     * @return GeneratorPoint
53
     */
54 293
    public function getGenerator()
55
    {
56 293
        return $this->generator;
57
    }
58
59
    /**
60
     * @param int|string $scalar
61
     * @param bool|false $compressed
62
     * @return PrivateKey
63
     */
64 125
    public function getPrivateKey($scalar, $compressed = false)
65
    {
66 125
        return new PrivateKey($this, $scalar, $compressed);
67
    }
68
69
    /**
70
     * @param PointInterface $point
71
     * @param bool|false $compressed
72
     * @return PublicKey
73
     */
74 105
    public function getPublicKey(PointInterface $point, $compressed = false)
75
    {
76 105
        return new PublicKey($this, $point, $compressed);
77
    }
78
79
    /**
80
     * @param int|string $r
81
     * @param int|string $s
82
     * @return Signature
83
     */
84
    public function getSignature($r, $s)
85
    {
86
        return new Signature($this, $r, $s);
87
    }
88
89
    /**
90
     * @param BufferInterface $messageHash
91
     * @param PublicKey $publicKey
92
     * @param Signature $signature
93
     * @return bool
94
     */
95 47
    private function doVerify(BufferInterface $messageHash, PublicKey $publicKey, Signature $signature)
96
    {
97 47
        $n = $this->getGenerator()->getOrder();
98 47
        $math = $this->getMath();
99 47
        $generator = $this->getGenerator();
100
101 47
        $one = gmp_init(1);
102 47
        $r = gmp_init($signature->getR());
103 47
        $s = gmp_init($signature->getS());
104 47
        if ($math->cmp($r, $one) < 1 || $math->cmp($r, $math->sub($n, $one)) > 0) {
105 3
            return false;
106
        }
107
108 44
        if ($math->cmp($s, $one) < 1 || $math->cmp($s, $math->sub($n, $one)) > 0) {
109
            return false;
110
        }
111
112 44
        $c = $math->inverseMod($s, $n);
113 44
        $u1 = $math->mod($math->mul(gmp_init($messageHash->getInt(), 10), $c), $n);
114 44
        $u2 = $math->mod($math->mul($r, $c), $n);
115 44
        $xy = $generator->mul($u1)->add($publicKey->getPoint()->mul($u2));
116 44
        $v = $math->mod($xy->getX(), $n);
117
118 44
        return $math->cmp($v, $r) === 0;
119
    }
120
121
    /**
122
     * @param BufferInterface $messageHash
123
     * @param PublicKeyInterface $publicKey
124
     * @param SignatureInterface $signature
125
     * @return bool
126
     */
127 47
    public function verify(BufferInterface $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
128
    {
129
        /** @var PublicKey $publicKey */
130
        /** @var Signature $signature */
131 47
        return $this->doVerify($messageHash, $publicKey, $signature);
0 ignored issues
show
Compatibility introduced by
$publicKey of type object<BitWasp\Bitcoin\C...Key\PublicKeyInterface> is not a sub-type of object<BitWasp\Bitcoin\C...l\PhpEcc\Key\PublicKey>. It seems like you assume a concrete implementation of the interface BitWasp\Bitcoin\Crypto\E...\Key\PublicKeyInterface to be always present.

This check looks for parameters that are defined as one type in their type hint or doc comment but seem to be used as a narrower type, i.e an implementation of an interface or a subclass.

Consider changing the type of the parameter or doing an instanceof check before assuming your parameter is of the expected type.

Loading history...
132
    }
133
134
    /**
135
     * @param BufferInterface $messageHash
136
     * @param PrivateKey $privateKey
137
     * @param RbgInterface|null $rbg
138
     * @return Signature
139
     */
140 46
    private function doSign(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
141
    {
142 46
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
143 46
        $randomK = $rbg->bytes(32);
144
145 46
        $math = $this->getMath();
146 46
        $generator = $this->getGenerator();
147 46
        $n = $generator->getOrder();
148
149 46
        $k = $math->mod(gmp_init($randomK->getInt(), 10), $n);
150 46
        $r = $generator->mul($k)->getX();
151
152 46
        if ($math->cmp($r, gmp_init(0)) === 0) {
153
            throw new \RuntimeException('Random number r = 0');
154
        }
155
156 46
        $s = $math->mod(
157 46
            $math->mul(
158 46
                $math->inverseMod($k, $n),
159 46
                $math->mod(
160 46
                    $math->add(
161 46
                        gmp_init($messageHash->getInt(), 10),
162 46
                        $math->mul(
163 46
                            gmp_init($privateKey->getSecretMultiplier(), 10),
164
                            $r
165 23
                        )
166 23
                    ),
167
                    $n
168 23
                )
169 23
            ),
170
            $n
171 23
        );
172
173 46
        if ($math->cmp($s, gmp_init(0)) === 0) {
174
            throw new \RuntimeException('Signature s = 0');
175
        }
176
177
        // if s is less than half the curve order, invert s
178 46
        if (!$this->validateSignatureElement($s, true)) {
179 24
            $s = $math->sub($n, $s);
180 13
        }
181
182 46
        return new Signature($this, gmp_strval($r, 10), gmp_strval($s, 10));
183
    }
184
185
    /**
186
     * @param BufferInterface $messageHash
187
     * @param PrivateKeyInterface $privateKey
188
     * @param RbgInterface $rbg
189
     * @return SignatureInterface
190
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
191
     */
192 46
    public function sign(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
193
    {
194
        /** @var PrivateKey $privateKey */
195 46
        return $this->doSign($messageHash, $privateKey, $rbg);
196
    }
197
198
    /**
199
     * @param BufferInterface $messageHash
200
     * @param CompactSignatureInterface $signature
201
     * @return PublicKey
202
     * @throws \Exception
203
     */
204 17
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature)
205
    {
206 17
        $math = $this->getMath();
207 17
        $G = $this->getGenerator();
208
209 17
        $zero = gmp_init(0);
210 17
        $one = gmp_init(1);
211
212 17
        $r = gmp_init($signature->getR());
213 17
        $s = gmp_init($signature->getS());
214 17
        $recGMP = gmp_init($signature->getRecoveryId(), 10);
215 17
        $isYEven = $math->cmp($math->bitwiseAnd($recGMP, $one), $zero) !== 0;
216 17
        $isSecondKey = $math->cmp($math->bitwiseAnd($recGMP, gmp_init(2)), $zero) !== 0;
217 17
        $curve = $G->getCurve();
218
219
        // Precalculate (p + 1) / 4 where p is the field order
220 17
        $p_over_four = $math->div($math->add($curve->getPrime(), $one), gmp_init(4));
221
222
        // 1.1 Compute x
223 17
        if (!$isSecondKey) {
224 17
            $x = $r;
225 9
        } else {
226 3
            $x = $math->add($r, $G->getOrder());
227
        }
228
229
        // 1.3 Convert x to point
230 17
        $alpha = $math->mod($math->add($math->add($math->pow($x, 3), $math->mul($curve->getA(), $x)), $curve->getB()), $curve->getPrime());
231 17
        $beta = $math->powmod($alpha, $p_over_four, $curve->getPrime());
232
233
        // If beta is even, but y isn't or vice versa, then convert it,
234
        // otherwise we're done and y == beta.
235 17
        if ($math->isEven($beta) === $isYEven) {
236 11
            $y = $math->sub($curve->getPrime(), $beta);
237 6
        } else {
238 9
            $y = $beta;
239
        }
240
241
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
242 17
        $R = $G->getCurve()->getPoint($x, $y);
243
244 17
        $point_negate = function (PointInterface $p) use ($math, $G) {
245 17
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), gmp_init('-1', 10)));
246 17
        };
247
248
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
249 17
        $rInv = $math->inverseMod($r, $G->getOrder());
250 17
        $eGNeg = $point_negate($G->mul(gmp_init($messageHash->getInt())));
251 17
        $Q = $R->mul($s)->add($eGNeg)->mul($rInv);
252
253
        // 1.6.2 Test Q as a public key
254 17
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
255 17
        if ($this->verify($messageHash, $Qk, $signature->convert())) {
256 14
            return $Qk;
257
        }
258
259 3
        throw new \Exception('Unable to recover public key');
260
    }
261
262
    /**
263
     * Attempt to calculate the public key recovery param by trial and error
264
     *
265
     * @param int $r
266
     * @param int $s
267
     * @param BufferInterface $messageHash
268
     * @param PublicKey $publicKey
269
     * @return int
270
     * @throws \Exception
271
     */
272 15
    public function calcPubKeyRecoveryParam($r, $s, BufferInterface $messageHash, PublicKey $publicKey)
273
    {
274 15
        $Q = $publicKey->getPoint();
275 15
        for ($i = 0; $i < 4; $i++) {
276
            try {
277 15
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
278 12
                if ($recover->getPoint()->equals($Q)) {
279 12
                    return $i;
280
                }
281 3
            } catch (\Exception $e) {
282 3
                continue;
283
            }
284
        }
285
286 3
        throw new \Exception('Failed to find valid recovery factor');
287
    }
288
289
    /**
290
     * @param BufferInterface $messageHash
291
     * @param PrivateKey $privateKey
292
     * @param RbgInterface|null $rbg
293
     * @return CompactSignature
294
     * @throws \Exception
295
     */
296 12
    private function doSignCompact(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
297
    {
298 12
        $sign = $this->sign($messageHash, $privateKey, $rbg);
299
300
        // calculate the recovery param
301
        // there should be a way to get this when signing too, but idk how ...
302 12
        return new CompactSignature(
303 6
            $this,
304 12
            $sign->getR(),
305 12
            $sign->getS(),
306 12
            $this->calcPubKeyRecoveryParam($sign->getR(), $sign->getS(), $messageHash, $privateKey->getPublicKey()),
307 12
            $privateKey->isCompressed()
308 6
        );
309
    }
310
311
    /**
312
     * @param PrivateKeyInterface $privateKey
313
     * @param BufferInterface $messageHash
314
     * @param RbgInterface $rbg
315
     * @return CompactSignature
316
     */
317 12
    public function signCompact(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
318
    {
319
        /** @var PrivateKey $privateKey */
320 12
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
321
    }
322
323
    /**
324
     * @param BufferInterface $privateKey
325
     * @return bool
326
     */
327 129
    public function validatePrivateKey(BufferInterface $privateKey)
328
    {
329 129
        $math = $this->math;
330 129
        $scalar = gmp_init($privateKey->getInt(), 10);
331 129
        return $math->cmp($scalar, gmp_init(0)) > 0 && $math->cmp($scalar, $this->getGenerator()->getOrder()) < 0;
332
    }
333
334
    /**
335
     * @param \GMP $element
336
     * @param bool $half
337
     * @return bool
338
     */
339 50
    public function validateSignatureElement(\GMP $element, $half = false)
340
    {
341 50
        $math = $this->getMath();
342 50
        $against = $this->getGenerator()->getOrder();
343 50
        if ($half) {
344 50
            $against = $math->rightShift($against, 1);
345 25
        }
346
347 50
        return $math->cmp($element, $against) < 0 && $math->cmp($element, gmp_init(0)) !== 0;
348
    }
349
350
    /**
351
     * @param BufferInterface $publicKey
352
     * @return PublicKeyInterface
353
     * @throws \Exception
354
     */
355 158
    public function publicKeyFromBuffer(BufferInterface $publicKey)
356
    {
357 158
        $compressed = $publicKey->getSize() == PublicKey::LENGTH_COMPRESSED;
358 158
        $xCoord = gmp_init($publicKey->slice(1, 32)->getInt(), 10);
359
360 158
        return new PublicKey(
361 79
            $this,
362 158
            $this->getGenerator()
363 158
                ->getCurve()
364 158
                ->getPoint(
365 79
                    $xCoord,
366 79
                    $compressed
0 ignored issues
show
Bug introduced by
It seems like $compressed ? $this->rec...(33, 32)->getInt(), 10) can also be of type resource; however, Mdanter\Ecc\Primitives\C...FpInterface::getPoint() does only seem to accept object<GMP>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
367 130
                    ? $this->recoverYfromX($xCoord, $publicKey->slice(0, 1)->getHex())
368 154
                    : gmp_init($publicKey->slice(33, 32)->getInt(), 10)
369 77
                ),
370
            $compressed
371 77
        );
372
    }
373
374
    /**
375
     * @param \GMP $xCoord
376
     * @param string $prefix
377
     * @return int|string
378
     * @throws \Exception
379
     */
380 102
    public function recoverYfromX(\GMP $xCoord, $prefix)
381
    {
382 102
        if (!in_array($prefix, array(PublicKey::KEY_COMPRESSED_ODD, PublicKey::KEY_COMPRESSED_EVEN))) {
383 4
            throw new \RuntimeException('Incorrect byte for a public key');
384
        }
385
386 98
        $math = $this->getMath();
387 98
        $curve = $this->getGenerator()->getCurve();
388 98
        $prime = $curve->getPrime();
389
390
        // Calculate first root
391 98
        $root0 = $math->getNumberTheory()->squareRootModP(
392 98
            $math->add(
393 98
                $math->powmod(
394 49
                    $xCoord,
395 98
                    gmp_init(3, 10),
396
                    $prime
397 49
                ),
398 98
                $curve->getB()
399 49
            ),
400
            $prime
401 49
        );
402
403
        // Depending on the byte, we expect the Y value to be even or odd.
404
        // We only calculate the second y root if it's needed.
405 98
        return (($prefix == PublicKey::KEY_COMPRESSED_EVEN) == $math->isEven($root0))
406 79
            ? $root0
407 98
            : $math->sub($prime, $root0);
408
    }
409
}
410