Completed
Pull Request — master (#229)
by thomas
123:03 queued 47:24
created

EcAdapter::associateSigs()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

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