Completed
Pull Request — master (#313)
by thomas
05:54
created

EcAdapter::getMath()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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 23
    public function __construct(Math $math, GeneratorPoint $generator)
38
    {
39 23
        $this->math = $math;
40 23
        $this->generator = $generator;
41 23
    }
42
43
    /**
44
     * @return Math
45
     */
46 328
    public function getMath()
47
    {
48 328
        return $this->math;
49
    }
50
51
    /**
52
     * @return GeneratorPoint
53
     */
54 149
    public function getGenerator()
55
    {
56 149
        return $this->generator;
57
    }
58
59
    /**
60
     * @param int|string $scalar
61
     * @param bool|false $compressed
62
     * @return PrivateKey
63
     */
64 65
    public function getPrivateKey($scalar, $compressed = false)
65
    {
66 65
        return new PrivateKey($this, $scalar, $compressed);
67
    }
68
69
    /**
70
     * @param PointInterface $point
71
     * @param bool|false $compressed
72
     * @return PublicKey
73
     */
74 55
    public function getPublicKey(PointInterface $point, $compressed = false)
75
    {
76 55
        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 26
    private function doVerify(BufferInterface $messageHash, PublicKey $publicKey, Signature $signature)
96
    {
97 26
        $n = $this->getGenerator()->getOrder();
98 26
        $math = $this->getMath();
99 26
        $generator = $this->getGenerator();
100
101 26
        $one = gmp_init(1);
102 4
        $r = gmp_init($signature->getR());
103
        $s = gmp_init($signature->getS());
104
        if ($math->cmp($r, $one) < 1 || $math->cmp($r, $math->sub($n, $one)) > 0) {
105 22
            return false;
106
        }
107
108
        if ($math->cmp($s, $one) < 1 || $math->cmp($s, $math->sub($n, $one)) > 0) {
109 22
            return false;
110 22
        }
111 22
112 22
        $c = $math->inverseMod($s, $n);
113 22
        $u1 = $math->mod($math->mul(gmp_init($messageHash->getInt(), 10), $c), $n);
114
        $u2 = $math->mod($math->mul($r, $c), $n);
115 22
        $xy = $generator->mul($u1)->add($publicKey->getPoint()->mul($u2));
116
        $v = $math->mod($xy->getX(), $n);
117
118
        return $math->cmp($v, $r) === 0;
119
    }
120
121
    /**
122
     * @param BufferInterface $messageHash
123
     * @param PublicKeyInterface $publicKey
124 26
     * @param SignatureInterface $signature
125
     * @return bool
126
     */
127
    public function verify(BufferInterface $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
128 26
    {
129
        /** @var PublicKey $publicKey */
130
        /** @var Signature $signature */
131
        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 23
     * @param RbgInterface|null $rbg
138
     * @return Signature
139 23
     */
140 23
    private function doSign(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
141
    {
142 23
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
143 23
        $randomK = $rbg->bytes(32);
144 23
145
        $math = $this->getMath();
146 23
        $generator = $this->getGenerator();
147 23
        $n = $generator->getOrder();
148
149 23
        $k = $math->mod(gmp_init($randomK->getInt(), 10), $n);
150
        $r = $generator->mul($k)->getX();
151
152
        if ($math->cmp($r, gmp_init(0)) === 0) {
153 23
            throw new \RuntimeException('Random number r = 0');
154 23
        }
155 23
156 23
        $s = $math->mod(
157 23
            $math->mul(
158 23
                $math->inverseMod($k, $n),
159 23
                $math->mod(
160 23
                    $math->add(
161
                        gmp_init($messageHash->getInt(), 10),
162
                        $math->mul(
163
                            gmp_init($privateKey->getSecretMultiplier(), 10),
164
                            $r
165
                        )
166
                    ),
167
                    $n
168
                )
169
            ),
170 23
            $n
171
        );
172
173
        if ($math->cmp($s, gmp_init(0)) === 0) {
174
            throw new \RuntimeException('Signature s = 0');
175 23
        }
176 9
177
        // if s is less than half the curve order, invert s
178
        if (!$this->validateSignatureElement($s, true)) {
179 23
            $s = $math->sub($n, $s);
180
        }
181
182
        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 23
     * @return SignatureInterface
190
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
191
     */
192 23
    public function sign(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
193
    {
194
        /** @var PrivateKey $privateKey */
195
        return $this->doSign($messageHash, $privateKey, $rbg);
196
    }
197
198
    /**
199
     * @param BufferInterface $messageHash
200
     * @param CompactSignatureInterface $signature
201 11
     * @return PublicKey
202
     * @throws \Exception
203 11
     */
204 11
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature)
205
    {
206 11
        $math = $this->getMath();
207 11
        $G = $this->getGenerator();
208 11
209
        $zero = gmp_init(0);
210
        $one = gmp_init(1);
211 11
212
        $r = gmp_init($signature->getR());
213
        $s = gmp_init($signature->getS());
214 11
        $recGMP = gmp_init($signature->getRecoveryId(), 10);
215 11
        $isYEven = $math->cmp($math->bitwiseAnd($recGMP, $one), $zero) !== 0;
216 3
        $isSecondKey = $math->cmp($math->bitwiseAnd($recGMP, gmp_init(2)), $zero) !== 0;
217 4
        $curve = $G->getCurve();
218
219
        // Precalculate (p + 1) / 4 where p is the field order
220
        $p_over_four = $math->div($math->add($curve->getPrime(), $one), gmp_init(4));
221 11
222 11
        // 1.1 Compute x
223
        if (!$isSecondKey) {
224
            $x = $r;
225
        } else {
226 11
            $x = $math->add($r, $G->getOrder());
227 10
        }
228 3
229 7
        // 1.3 Convert x to point
230
        $alpha = $math->mod($math->add($math->add($math->pow($x, 3), $math->mul($curve->getA(), $x)), $curve->getB()), $curve->getPrime());
231
        $beta = $math->powmod($alpha, $p_over_four, $curve->getPrime());
232
233 11
        // If beta is even, but y isn't or vice versa, then convert it,
234
        // otherwise we're done and y == beta.
235 11
        if ($math->isEven($beta) === $isYEven) {
236 11
            $y = $math->sub($curve->getPrime(), $beta);
237 11
        } else {
238
            $y = $beta;
239
        }
240 11
241 11
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
242 11
        $R = $G->getCurve()->getPoint($x, $y);
243
244
        $point_negate = function (PointInterface $p) use ($math, $G) {
245 11
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), gmp_init('-1', 10)));
246 11
        };
247 7
248
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
249
        $rInv = $math->inverseMod($r, $G->getOrder());
250 4
        $eGNeg = $point_negate($G->mul(gmp_init($messageHash->getInt())));
251
        $Q = $R->mul($s)->add($eGNeg)->mul($rInv);
252
253
        // 1.6.2 Test Q as a public key
254
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
255
        if ($this->verify($messageHash, $Qk, $signature->convert())) {
256
            return $Qk;
257
        }
258
259
        throw new \Exception('Unable to recover public key');
260
    }
261
262
    /**
263 10
     * Attempt to calculate the public key recovery param by trial and error
264
     *
265 10
     * @param int $r
266 10
     * @param int $s
267
     * @param BufferInterface $messageHash
268 10
     * @param PublicKey $publicKey
269 6
     * @return int
270 6
     * @throws \Exception
271
     */
272 4
    public function calcPubKeyRecoveryParam($r, $s, BufferInterface $messageHash, PublicKey $publicKey)
273 4
    {
274
        $Q = $publicKey->getPoint();
275
        for ($i = 0; $i < 4; $i++) {
276
            try {
277 4
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
278
                if ($recover->getPoint()->equals($Q)) {
279
                    return $i;
280
                }
281
            } catch (\Exception $e) {
282
                continue;
283
            }
284
        }
285
286
        throw new \Exception('Failed to find valid recovery factor');
287 6
    }
288
289 6
    /**
290
     * @param BufferInterface $messageHash
291
     * @param PrivateKey $privateKey
292
     * @param RbgInterface|null $rbg
293 6
     * @return CompactSignature
294
     * @throws \Exception
295 6
     */
296 6
    private function doSignCompact(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
297 6
    {
298 6
        $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
        return new CompactSignature(
303
            $this,
304
            $sign->getR(),
305
            $sign->getS(),
306
            $this->calcPubKeyRecoveryParam($sign->getR(), $sign->getS(), $messageHash, $privateKey->getPublicKey()),
307
            $privateKey->isCompressed()
308 6
        );
309
    }
310
311 6
    /**
312
     * @param PrivateKeyInterface $privateKey
313
     * @param BufferInterface $messageHash
314
     * @param RbgInterface $rbg
315
     * @return CompactSignature
316
     */
317
    public function signCompact(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
318 67
    {
319
        /** @var PrivateKey $privateKey */
320 67
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
321 67
    }
322 67
323
    /**
324
     * @param BufferInterface $privateKey
325
     * @return bool
326
     */
327
    public function validatePrivateKey(BufferInterface $privateKey)
328
    {
329
        $math = $this->math;
330 25
        $scalar = gmp_init($privateKey->getInt(), 10);
331
        return $math->cmp($scalar, gmp_init(0)) > 0 && $math->cmp($scalar, $this->getGenerator()->getOrder()) < 0;
332 25
    }
333 25
334 25
    /**
335 25
     * @param \GMP $element
336
     * @param bool $half
337
     * @return bool
338 25
     */
339
    public function validateSignatureElement(\GMP $element, $half = false)
340
    {
341
        $math = $this->getMath();
342
        $against = $this->getGenerator()->getOrder();
343
        if ($half) {
344
            $against = $math->rightShift($against, 1);
345
        }
346 79
347
        return $math->cmp($element, $against) < 0 && $math->cmp($element, gmp_init(0)) !== 0;
348 79
    }
349 79
350
    /**
351 79
     * @param BufferInterface $publicKey
352
     * @return PublicKeyInterface
353 79
     * @throws \Exception
354 79
     */
355 79
    public function publicKeyFromBuffer(BufferInterface $publicKey)
356
    {
357 79
        $compressed = $publicKey->getSize() == PublicKey::LENGTH_COMPRESSED;
358 51
        $xCoord = gmp_init($publicKey->slice(1, 32)->getInt(), 10);
359 77
360
        return new PublicKey(
361
            $this,
362
            $this->getGenerator()
363
                ->getCurve()
364
                ->getPoint(
365
                    $xCoord,
366
                    $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
                    ? $this->recoverYfromX($xCoord, $publicKey->slice(0, 1)->getHex())
368
                    : gmp_init($publicKey->slice(33, 32)->getInt(), 10)
369
                ),
370
            $compressed
371 51
        );
372
    }
373 51
374 2
    /**
375
     * @param \GMP $xCoord
376
     * @param string $prefix
377 49
     * @return int|string
378 49
     * @throws \Exception
379 49
     */
380
    public function recoverYfromX(\GMP $xCoord, $prefix)
381
    {
382 49
        if (!in_array($prefix, array(PublicKey::KEY_COMPRESSED_ODD, PublicKey::KEY_COMPRESSED_EVEN))) {
383 49
            throw new \RuntimeException('Incorrect byte for a public key');
384 49
        }
385
386 49
        $math = $this->getMath();
387
        $curve = $this->getGenerator()->getCurve();
388
        $prime = $curve->getPrime();
389 49
390
        // Calculate first root
391
        $root0 = $math->getNumberTheory()->squareRootModP(
392
            $math->add(
393
                $math->powmod(
394
                    $xCoord,
395
                    gmp_init(3, 10),
396 49
                    $prime
397 30
                ),
398 49
                $curve->getB()
399
            ),
400
            $prime
401
        );
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
        return (($prefix == PublicKey::KEY_COMPRESSED_EVEN) == $math->isEven($root0))
406
            ? $root0
407
            : $math->sub($prime, $root0);
408
    }
409
}
410