Completed
Push — master ( b98633...e1c430 )
by thomas
30:03 queued 26:59
created

EcAdapter::verify()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 2
cts 2
cp 1
rs 9.4285
cc 1
eloc 2
nc 1
nop 3
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\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 57
    public function __construct(Math $math, GeneratorPoint $generator)
38
    {
39 57
        $this->math = $math;
40 57
        $this->generator = $generator;
41 57
    }
42
43
    /**
44
     * @return Math
45
     */
46 1008
    public function getMath()
47
    {
48 1008
        return $this->math;
49
    }
50
51
    /**
52
     * @return GeneratorPoint
53
     */
54 483
    public function getGenerator()
55
    {
56 483
        return $this->generator;
57
    }
58
59
    /**
60
     * @param int|string $scalar
61
     * @param bool|false $compressed
62
     * @return PrivateKey
63
     */
64 222
    public function getPrivateKey($scalar, $compressed = false)
65
    {
66 222
        return new PrivateKey($this, $scalar, $compressed);
67
    }
68
69
    /**
70
     * @param PointInterface $point
71
     * @param bool|false $compressed
72
     * @return PublicKey
73
     */
74 189
    public function getPublicKey(PointInterface $point, $compressed = false)
75
    {
76 189
        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 75
    private function doVerify(BufferInterface $messageHash, PublicKey $publicKey, Signature $signature)
96
    {
97 75
        $n = $this->getGenerator()->getOrder();
98 75
        $math = $this->getMath();
99 75
        $generator = $this->getGenerator();
100
101 75
        if ($math->cmp($signature->getR(), 1) < 1 || $math->cmp($signature->getR(), $math->sub($n, 1)) > 0) {
102 6
            return false;
103
        }
104
105 69
        if ($math->cmp($signature->getS(), 1) < 1 || $math->cmp($signature->getS(), $math->sub($n, 1)) > 0) {
106
            return false;
107
        }
108
109 69
        $c = $math->inverseMod($signature->getS(), $n);
110 69
        $u1 = $math->mod($math->mul($messageHash->getInt(), $c), $n);
111 69
        $u2 = $math->mod($math->mul($signature->getR(), $c), $n);
112 69
        $xy = $generator->mul($u1)->add($publicKey->getPoint()->mul($u2));
113 69
        $v = $math->mod($xy->getX(), $n);
114
115 69
        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 75
    public function verify(BufferInterface $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
125
    {
126
        /** @var PublicKey $publicKey */
127
        /** @var Signature $signature */
128 75
        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 96
    private function doSign(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
138
    {
139 96
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
140 96
        $randomK = $rbg->bytes(32);
141
142 96
        $math = $this->getMath();
143 96
        $generator = $this->getGenerator();
144 96
        $n = $generator->getOrder();
145
146 96
        $k = $math->mod($randomK->getInt(), $n);
147 96
        $r = $generator->mul($k)->getX();
148
149 96
        if ($math->cmp($r, 0) === 0) {
150
            throw new \RuntimeException('Random number r = 0');
151
        }
152
153 96
        $s = $math->mod(
154 96
            $math->mul(
155 96
                $math->inverseMod($k, $n),
156 96
                $math->mod(
157 96
                    $math->add(
158 96
                        $messageHash->getInt(),
159 96
                        $math->mul(
160 96
                            $privateKey->getSecretMultiplier(),
161
                            $r
162 96
                        )
163 96
                    ),
164
                    $n
165 96
                )
166 96
            ),
167
            $n
168 96
        );
169
170 96
        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 96
        if (!$this->validateSignatureElement($s, true)) {
176 43
            $s = $math->sub($n, $s);
177 43
        }
178
179 96
        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 96
    public function sign(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
190
    {
191
        /** @var PrivateKey $privateKey */
192 96
        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 27
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature)
202
    {
203 27
        $math = $this->getMath();
204 27
        $G = $this->getGenerator();
205
206 27
        $isYEven = $math->cmp($math->bitwiseAnd($signature->getRecoveryId(), 1), 0) !== 0;
207 27
        $isSecondKey = $math->cmp($math->bitwiseAnd($signature->getRecoveryId(), 2), 0) !== 0;
208 27
        $curve = $G->getCurve();
209
210
        // Precalculate (p + 1) / 4 where p is the field order
211 27
        $p_over_four = $math->div($math->add($curve->getPrime(), 1), 4);
212
213
        // 1.1 Compute x
214 27
        if (!$isSecondKey) {
215 27
            $x = $signature->getR();
216 27
        } else {
217 6
            $x = $math->add($signature->getR(), $G->getOrder());
218
        }
219
220
        // 1.3 Convert x to point
221 27
        $alpha = $math->mod($math->add($math->add($math->pow($x, 3), $math->mul($curve->getA(), $x)), $curve->getB()), $curve->getPrime());
222 27
        $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 27
        if ($math->isEven($beta) === $isYEven) {
227 22
            $y = $math->sub($curve->getPrime(), $beta);
228 22
        } else {
229 19
            $y = $beta;
230
        }
231
232
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
233 27
        $R = $G->getCurve()->getPoint($x, $y);
234
235 27
        $point_negate = function (PointInterface $p) use ($math, $G) {
236 27
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), -1));
237 27
        };
238
239
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
240 27
        $rInv = $math->inverseMod($signature->getR(), $G->getOrder());
241 27
        $eGNeg = $point_negate($G->mul($messageHash->getInt()));
242 27
        $Q = $R->mul($signature->getS())->add($eGNeg)->mul($rInv);
243
244
        // 1.6.2 Test Q as a public key
245 27
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
246 27
        if ($this->verify($messageHash, $Qk, $signature->convert())) {
247 21
            return $Qk;
248
        }
249
250 6
        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 24
    public function calcPubKeyRecoveryParam($r, $s, BufferInterface $messageHash, PublicKey $publicKey)
264
    {
265 24
        $Q = $publicKey->getPoint();
266 24
        for ($i = 0; $i < 4; $i++) {
267
            try {
268 24
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
269 18
                if ($recover->getPoint()->equals($Q)) {
270 18
                    return $i;
271
                }
272 14
            } catch (\Exception $e) {
273 6
                continue;
274
            }
275 8
        }
276
277 6
        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 18
    private function doSignCompact(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
288
    {
289 18
        $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 18
        return new CompactSignature(
294 18
            $this,
295 18
            $sign->getR(),
296 18
            $sign->getS(),
297 18
            $this->calcPubKeyRecoveryParam($sign->getR(), $sign->getS(), $messageHash, $privateKey->getPublicKey()),
298 18
            $privateKey->isCompressed()
299 18
        );
300
    }
301
302
    /**
303
     * @param PrivateKeyInterface $privateKey
304
     * @param BufferInterface $messageHash
305
     * @param RbgInterface $rbg
306
     * @return CompactSignature
307
     */
308 18
    public function signCompact(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
309
    {
310
        /** @var PrivateKey $privateKey */
311 18
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
312
    }
313
314
    /**
315
     * @param BufferInterface $privateKey
316
     * @return bool
317
     */
318 228
    public function validatePrivateKey(BufferInterface $privateKey)
319
    {
320 228
        $math = $this->math;
321 228
        $scalar = $privateKey->getInt();
322 228
        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 102
    public function validateSignatureElement($element, $half = false)
331
    {
332 102
        $math = $this->getMath();
333 102
        $against = $this->getGenerator()->getOrder();
334 102
        if ($half) {
335 102
            $against = $math->rightShift($against, 1);
336 102
        }
337
338 102
        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 258
    public function publicKeyFromBuffer(BufferInterface $publicKey)
347
    {
348 258
        $compressed = $publicKey->getSize() == PublicKey::LENGTH_COMPRESSED;
349 258
        $xCoord = $publicKey->slice(1, 32)->getInt();
350
351 258
        return new PublicKey(
352 258
            $this,
353 258
            $this->getGenerator()
354 258
                ->getCurve()
355 258
                ->getPoint(
356 258
                    $xCoord,
357
                    $compressed
358 258
                    ? $this->recoverYfromX($xCoord, $publicKey->slice(0, 1)->getHex())
359 252
                    : $publicKey->slice(33, 32)->getInt()
360 252
                ),
361
            $compressed
362 252
        );
363
    }
364
365
    /**
366
     * @param int|string $xCoord
367
     * @param string $prefix
368
     * @return int|string
369
     * @throws \Exception
370
     */
371 153
    public function recoverYfromX($xCoord, $prefix)
372
    {
373 153
        if (!in_array($prefix, array(PublicKey::KEY_COMPRESSED_ODD, PublicKey::KEY_COMPRESSED_EVEN))) {
374 6
            throw new \RuntimeException('Incorrect byte for a public key');
375
        }
376
377 147
        $math = $this->getMath();
378 147
        $curve = $this->getGenerator()->getCurve();
379 147
        $prime = $curve->getPrime();
380
381
        // Calculate first root
382 147
        $root0 = $math->getNumberTheory()->squareRootModP(
383 147
            $math->add(
384 147
                $math->powMod(
385 147
                    $xCoord,
386 147
                    3,
387
                    $prime
388 147
                ),
389 147
                $curve->getB()
390 147
            ),
391
            $prime
392 147
        );
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 147
        return (($prefix == PublicKey::KEY_COMPRESSED_EVEN) == $math->isEven($root0))
397 147
            ? $root0
398 147
            : $math->sub($prime, $root0);
399
    }
400
}
401