Completed
Push — master ( 545d02...a22894 )
by thomas
14:41
created

EcAdapter::recoverYfromX()   B

Complexity

Conditions 3
Paths 3

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 3.0987

Importance

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