Completed
Push — master ( 97b66a...92b407 )
by thomas
29:00
created

EcAdapter::getPublicKey()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

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