Completed
Push — master ( 70da09...3502ce )
by thomas
122:11 queued 112:52
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 45
    private function doVerify(Buffer $messageHash, PublicKey $publicKey, Signature $signature)
96
    {
97 45
        $n = $this->getGenerator()->getOrder();
98 45
        $math = $this->getMath();
99 45
        $generator = $this->getGenerator();
100
101 45
        if ($math->cmp($signature->getR(), 1) < 1 || $math->cmp($signature->getR(), $math->sub($n, 1)) > 0) {
102 6
            return false;
103
        }
104
105 39
        if ($math->cmp($signature->getS(), 1) < 1 || $math->cmp($signature->getS(), $math->sub($n, 1)) > 0) {
106
            return false;
107
        }
108
109 39
        $c = $math->inverseMod($signature->getS(), $n);
110 39
        $u1 = $math->mod($math->mul($messageHash->getInt(), $c), $n);
111 39
        $u2 = $math->mod($math->mul($signature->getR(), $c), $n);
112 39
        $xy = $generator->mul($u1)->add($publicKey->getPoint()->mul($u2));
113 39
        $v = $math->mod($xy->getX(), $n);
114
115 39
        return $math->cmp($v, $signature->getR()) === 0;
116
    }
117
118
    /**
119
     * @param Buffer $messageHash
120
     * @param PublicKeyInterface $publicKey
121
     * @param SignatureInterface $signature
122
     * @return bool
123
     */
124 45
    public function verify(Buffer $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
125
    {
126
        /** @var PublicKey $publicKey */
127
        /** @var Signature $signature */
128 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...
129
    }
130
131
    /**
132
     * @param Buffer $messageHash
133
     * @param PrivateKey $privateKey
134
     * @param RbgInterface|null $rbg
135
     * @return Signature
136
     */
137 69
    private function doSign(Buffer $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
138
    {
139 69
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
140 69
        $randomK = $rbg->bytes(32);
141
142 69
        $math = $this->getMath();
143 69
        $generator = $this->getGenerator();
144 69
        $n = $generator->getOrder();
145
146 69
        $k = $math->mod($randomK->getInt(), $n);
147 69
        $r = $generator->mul($k)->getX();
148
149 69
        if ($math->cmp($r, 0) === 0) {
150
            throw new \RuntimeException('Random number r = 0');
151
        }
152
153 69
        $s = $math->mod(
154 69
            $math->mul(
155 69
                $math->inverseMod($k, $n),
156 69
                $math->mod(
157 69
                    $math->add(
158 69
                        $messageHash->getInt(),
159 69
                        $math->mul(
160 69
                            $privateKey->getSecretMultiplier(),
161
                            $r
162 69
                        )
163 69
                    ),
164
                    $n
165 69
                )
166 69
            ),
167
            $n
168 69
        );
169
170 69
        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 69
        if (!$this->validateSignatureElement($s, true)) {
176 40
            $s = $math->sub($n, $s);
177 40
        }
178
179 69
        return new Signature($this, $r, $s);
180
    }
181
182
    /**
183
     * @param Buffer $messageHash
184
     * @param PrivateKeyInterface $privateKey
185
     * @param RbgInterface $rbg
186
     * @return SignatureInterface
187
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
188
     */
189 69
    public function sign(Buffer $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
190
    {
191
        /** @var PrivateKey $privateKey */
192 69
        return $this->doSign($messageHash, $privateKey, $rbg);
193
    }
194
195
    /**
196
     * @param Buffer $messageHash
197
     * @param CompactSignatureInterface $signature
198
     * @return PublicKey
199
     * @throws \Exception
200
     */
201 27
    public function recover(Buffer $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 20
            $y = $math->sub($curve->getPrime(), $beta);
228 20
        } 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 int|string     $r
257
     * @param int|string     $s
258
     * @param Buffer $messageHash
259
     * @param PublicKey $publicKey
260
     * @return int
261
     * @throws \Exception
262
     */
263 24
    public function calcPubKeyRecoveryParam($r, $s, Buffer $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 12
            } catch (\Exception $e) {
273 6
                continue;
274
            }
275 6
        }
276
277 6
        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 18
    private function doSignCompact(Buffer $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 Buffer $messageHash
305
     * @param RbgInterface $rbg
306
     * @return CompactSignature
307
     */
308 18
    public function signCompact(Buffer $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
309
    {
310
        /** @var PrivateKey $privateKey */
311 18
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
312
    }
313
314
    /**
315
     * @param Buffer $privateKey
316
     * @return bool
317
     */
318 243
    public function validatePrivateKey(Buffer $privateKey)
319
    {
320 243
        $math = $this->math;
321 243
        $scalar = $privateKey->getInt();
322 243
        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 75
    public function validateSignatureElement($element, $half = false)
331
    {
332 75
        $math = $this->getMath();
333 75
        $against = $this->getGenerator()->getOrder();
334 75
        if ($half) {
335 75
            $against = $math->rightShift($against, 1);
336 75
        }
337
338 75
        return $math->cmp($element, $against) < 0 && $math->cmp($element, 0) !== 0;
339
    }
340
341
    /**
342
     * @param Buffer $publicKey
343
     * @return PublicKeyInterface
344
     * @throws \Exception
345
     */
346 234
    public function publicKeyFromBuffer(Buffer $publicKey)
347
    {
348 234
        $compressed = $publicKey->getSize() == PublicKey::LENGTH_COMPRESSED;
349 234
        $xCoord = $publicKey->slice(1, 32)->getInt();
350
351 234
        return new PublicKey(
352 234
            $this,
353 234
            $this->getGenerator()
354 234
                ->getCurve()
355 234
                ->getPoint(
356 234
                    $xCoord,
357
                    $compressed
358 234
                    ? $this->recoverYfromX($xCoord, $publicKey->slice(0, 1)->getHex())
359 228
                    : $publicKey->slice(33, 32)->getInt()
360 228
                ),
361
            $compressed
362 228
        );
363
    }
364
365
    /**
366
     * @param int|string $xCoord
367
     * @param string $prefix
368
     * @return int|string
369
     * @throws \Exception
370
     */
371 126
    public function recoverYfromX($xCoord, $prefix)
372
    {
373 126
        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 120
        $math = $this->getMath();
378 120
        $curve = $this->getGenerator()->getCurve();
379 120
        $prime = $curve->getPrime();
380
381
        // Calculate first root
382 120
        $root0 = $math->getNumberTheory()->squareRootModP(
383 120
            $math->add(
384 120
                $math->powMod(
385 120
                    $xCoord,
386 120
                    3,
387
                    $prime
388 120
                ),
389 120
                $curve->getB()
390 120
            ),
391
            $prime
392 120
        );
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 120
        return (($prefix == PublicKey::KEY_COMPRESSED_EVEN) == $math->isEven($root0))
397 120
            ? $root0
398 120
            : $math->sub($prime, $root0);
399
    }
400
}
401