Completed
Push — master ( 2a5c77...6a30c1 )
by thomas
52:06 queued 14:02
created

EcAdapter::recover()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 57
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 33
CRAP Score 4

Importance

Changes 0
Metric Value
cc 4
eloc 32
nc 8
nop 2
dl 0
loc 57
ccs 33
cts 33
cp 1
crap 4
rs 9.0309
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\Crypto\Signature\Signer;
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 34
    public function __construct(Math $math, GeneratorPoint $generator)
39
    {
40 34
        $this->math = $math;
41 34
        $this->generator = $generator;
42 34
    }
43
44
    /**
45
     * @return Math
46
     */
47 430
    public function getMath()
48
    {
49 430
        return $this->math;
50
    }
51
52
    /**
53
     * @return GeneratorPoint
54
     */
55 298
    public function getGenerator()
56
    {
57 298
        return $this->generator;
58
    }
59
60
    /**
61
     * @param \GMP $scalar
62
     * @param bool|false $compressed
63
     * @return PrivateKey
64
     */
65 96
    public function getPrivateKey(\GMP $scalar, $compressed = false)
66
    {
67 96
        return new PrivateKey($this, $scalar, $compressed);
68
    }
69
70
    /**
71
     * @param PointInterface $point
72
     * @param bool|false $compressed
73
     * @return PublicKey
74
     */
75 130
    public function getPublicKey(PointInterface $point, $compressed = false)
76
    {
77 130
        return new PublicKey($this, $point, $compressed);
78
    }
79
80
    /**
81
     * @param \GMP $r
82
     * @param \GMP $s
83
     * @return Signature
84
     */
85
    public function getSignature(\GMP $r, \GMP $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 202
    private function doVerify(BufferInterface $messageHash, PublicKey $publicKey, Signature $signature)
97
    {
98 202
        $hash = gmp_init($messageHash->getHex(), 16);
99 202
        $signer = new Signer($this->math);
100 202
        return $signer->verify($publicKey, $signature, $hash);
101
    }
102
103
    /**
104
     * @param BufferInterface $messageHash
105
     * @param PublicKeyInterface $publicKey
106
     * @param SignatureInterface $signature
107
     * @return bool
108
     */
109 202
    public function verify(BufferInterface $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
110
    {
111
        /** @var PublicKey $publicKey */
112
        /** @var Signature $signature */
113 202
        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...
114
    }
115
116
    /**
117
     * @param BufferInterface $messageHash
118
     * @param PrivateKey $privateKey
119
     * @param RbgInterface|null $rbg
120
     * @return Signature
121
     */
122 74
    private function doSign(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
123
    {
124 74
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
125 74
        $randomK = gmp_init($rbg->bytes(32)->getHex(), 16);
126 74
        $hash = gmp_init($messageHash->getHex(), 16);
127
128 74
        $signer = new Signer($this->math);
129 74
        $signature = $signer->sign($privateKey, $hash, $randomK);
130 74
        $s = $signature->getS();
131
132
        // if s is less than half the curve order, invert s
133 74
        if (!$this->validateSignatureElement($s, true)) {
134 39
            $s = $this->math->sub($this->generator->getOrder(), $s);
135 20
        }
136
137 74
        return new Signature($this, $signature->getR(), $s);
0 ignored issues
show
Bug introduced by
It seems like $s defined by $this->math->sub($this->...erator->getOrder(), $s) on line 134 can also be of type resource; however, BitWasp\Bitcoin\Crypto\E...ignature::__construct() does only seem to accept object<GMP>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
138
    }
139
140
    /**
141
     * @param BufferInterface $messageHash
142
     * @param PrivateKeyInterface $privateKey
143
     * @param RbgInterface $rbg
144
     * @return SignatureInterface
145
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
146
     */
147 74
    public function sign(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
148
    {
149
        /** @var PrivateKey $privateKey */
150 74
        return $this->doSign($messageHash, $privateKey, $rbg);
151
    }
152
153
    /**
154
     * @param BufferInterface $messageHash
155
     * @param CompactSignatureInterface $signature
156
     * @return PublicKey
157
     * @throws \Exception
158
     */
159 20
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature)
160
    {
161 20
        $math = $this->getMath();
162 20
        $G = $this->getGenerator();
163
164 20
        $zero = gmp_init(0);
165 20
        $one = gmp_init(1);
166
167 20
        $r = $signature->getR();
168 20
        $s = $signature->getS();
169 20
        $recGMP = gmp_init($signature->getRecoveryId(), 10);
170 20
        $isYEven = $math->cmp($math->bitwiseAnd($recGMP, $one), $zero) !== 0;
171 20
        $isSecondKey = $math->cmp($math->bitwiseAnd($recGMP, gmp_init(2)), $zero) !== 0;
172 20
        $curve = $G->getCurve();
173
174
        // Precalculate (p + 1) / 4 where p is the field order
175 20
        $p_over_four = $math->div($math->add($curve->getPrime(), $one), gmp_init(4));
176
177
        // 1.1 Compute x
178 20
        if (!$isSecondKey) {
179 20
            $x = $r;
180 10
        } else {
181 6
            $x = $math->add($r, $G->getOrder());
182
        }
183
184
        // 1.3 Convert x to point
185 20
        $alpha = $math->mod($math->add($math->add($math->pow($x, 3), $math->mul($curve->getA(), $x)), $curve->getB()), $curve->getPrime());
0 ignored issues
show
Bug introduced by
It seems like $x defined by $math->add($r, $G->getOrder()) on line 181 can also be of type resource; however, Mdanter\Ecc\Math\GmpMath::pow() does only seem to accept object<GMP>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $x defined by $math->add($r, $G->getOrder()) on line 181 can also be of type resource; however, Mdanter\Ecc\Math\GmpMath::mul() does only seem to accept object<GMP>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
186 20
        $beta = $math->powmod($alpha, $p_over_four, $curve->getPrime());
187
188
        // If beta is even, but y isn't or vice versa, then convert it,
189
        // otherwise we're done and y=beta.
190 20
        if ($math->isEven($beta) === $isYEven) {
191 18
            $y = $math->sub($curve->getPrime(), $beta);
192 9
        } else {
193 14
            $y = $beta;
194
        }
195
196
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
197 20
        $R = $G->getCurve()->getPoint($x, $y);
0 ignored issues
show
Bug introduced by
It seems like $x defined by $math->add($r, $G->getOrder()) on line 181 can also be of type resource; however, Mdanter\Ecc\Primitives\C...FpInterface::getPoint() does only seem to accept object<GMP>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
198
199 20
        $point_negate = function (PointInterface $p) use ($math, $G) {
200 20
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), gmp_init('-1', 10)));
201 20
        };
202
203
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
204 20
        $rInv = $math->inverseMod($r, $G->getOrder());
205 20
        $eGNeg = $point_negate($G->mul($messageHash->getGmp()));
206 20
        $Q = $R->mul($s)->add($eGNeg)->mul($rInv);
207
208
        // 1.6.2 Test Q as a public key
209 20
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
210 20
        if ($this->verify($messageHash, $Qk, $signature->convert())) {
211 14
            return $Qk;
212
        }
213
214 6
        throw new \Exception('Unable to recover public key');
215
    }
216
217
    /**
218
     * Attempt to calculate the public key recovery param by trial and error
219
     *
220
     * @param \GMP $r
221
     * @param \GMP $s
222
     * @param BufferInterface $messageHash
223
     * @param PublicKey $publicKey
224
     * @return int
225
     * @throws \Exception
226
     */
227 18
    public function calcPubKeyRecoveryParam(\GMP $r, \GMP $s, BufferInterface $messageHash, PublicKey $publicKey)
228
    {
229 18
        $Q = $publicKey->getPoint();
230 18
        for ($i = 0; $i < 4; $i++) {
231
            try {
232 18
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
233 12
                if ($Q->equals($recover->getPoint())) {
234 12
                    return $i;
235
                }
236 10
            } catch (\Exception $e) {
237 6
                continue;
238
            }
239 4
        }
240
241 6
        throw new \Exception('Failed to find valid recovery factor');
242
    }
243
244
    /**
245
     * @param BufferInterface $messageHash
246
     * @param PrivateKey $privateKey
247
     * @param RbgInterface|null $rbg
248
     * @return CompactSignature
249
     * @throws \Exception
250
     */
251 12
    private function doSignCompact(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
252
    {
253 12
        $sign = $this->sign($messageHash, $privateKey, $rbg);
254
255
        // calculate the recovery param
256
        // there should be a way to get this when signing too, but idk how ...
257 12
        return new CompactSignature(
258 6
            $this,
259 12
            $sign->getR(),
260 12
            $sign->getS(),
261 12
            $this->calcPubKeyRecoveryParam($sign->getR(), $sign->getS(), $messageHash, $privateKey->getPublicKey()),
262 12
            $privateKey->isCompressed()
263 6
        );
264
    }
265
266
    /**
267
     * @param PrivateKeyInterface $privateKey
268
     * @param BufferInterface $messageHash
269
     * @param RbgInterface $rbg
270
     * @return CompactSignature
271
     */
272 12
    public function signCompact(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
273
    {
274
        /** @var PrivateKey $privateKey */
275 12
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
276
    }
277
278
    /**
279
     * @param BufferInterface $privateKey
280
     * @return bool
281
     */
282 100
    public function validatePrivateKey(BufferInterface $privateKey)
283
    {
284 100
        $math = $this->math;
285 100
        $scalar = $privateKey->getGmp();
286 100
        return $math->cmp($scalar, gmp_init(0)) > 0 && $math->cmp($scalar, $this->getGenerator()->getOrder()) < 0;
287
    }
288
289
    /**
290
     * @param \GMP $element
291
     * @param bool $half
292
     * @return bool
293
     */
294 80
    public function validateSignatureElement(\GMP $element, $half = false)
295
    {
296 80
        $math = $this->getMath();
297 80
        $against = $this->getGenerator()->getOrder();
298 80
        if ($half) {
299 80
            $against = $math->rightShift($against, 1);
300 40
        }
301
302 80
        return $math->cmp($element, $against) < 0 && $math->cmp($element, gmp_init(0)) !== 0;
303
    }
304
305
    /**
306
     * @param BufferInterface $publicKey
307
     * @return PublicKeyInterface
308
     * @throws \Exception
309
     */
310 308
    public function publicKeyFromBuffer(BufferInterface $publicKey)
311
    {
312 308
        $prefix = $publicKey->slice(0, 1)->getBinary();
313 308
        $size = $publicKey->getSize();
314 308
        $compressed = false;
315 308
        if ($prefix === PublicKey::KEY_UNCOMPRESSED || $prefix === "\x06" || $prefix === "\x07") {
316 136
            if ($size !== PublicKey::LENGTH_UNCOMPRESSED) {
317 69
                throw new \Exception('Invalid length for uncompressed key');
318
            }
319 246
        } else if ($prefix === PublicKey::KEY_COMPRESSED_EVEN || $prefix === PublicKey::KEY_COMPRESSED_ODD) {
320 184
            if ($size !== PublicKey::LENGTH_COMPRESSED) {
321
                throw new \Exception('Invalid length for compressed key');
322
            }
323 184
            $compressed = true;
324 92
        } else {
325 2
            throw new \Exception('Unknown public key prefix');
326
        }
327
        
328 304
        $x = $publicKey->slice(1, 32)->getGmp();
329 304
        $curve = $this->generator->getCurve();
330 152
        $y = $compressed
331 244
            ? $curve->recoverYfromX($prefix === PublicKey::KEY_COMPRESSED_ODD, $x)
332 304
            : $publicKey->slice(33, 32)->getGmp();
333
334 304
        return new PublicKey(
335 152
            $this,
336 304
            $curve->getPoint($x, $y),
337 152
            $compressed,
338
            $prefix
339 152
        );
340
    }
341
}
342