Completed
Pull Request — master (#318)
by thomas
72:01
created

EcAdapter::doVerify()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 1
eloc 4
c 3
b 0
f 0
nc 1
nop 3
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 1
rs 9.4285
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 19
     */
38
    public function __construct(Math $math, GeneratorPoint $generator)
39 19
    {
40 19
        $this->math = $math;
41 19
        $this->generator = $generator;
42
    }
43
44
    /**
45
     * @return Math
46 638
     */
47
    public function getMath()
48 638
    {
49
        return $this->math;
50
    }
51
52
    /**
53
     * @return GeneratorPoint
54 293
     */
55
    public function getGenerator()
56 293
    {
57
        return $this->generator;
58
    }
59
60
    /**
61
     * @param \GMP $scalar
62
     * @param bool|false $compressed
63
     * @return PrivateKey
64 125
     */
65
    public function getPrivateKey(\GMP $scalar, $compressed = false)
66 125
    {
67
        return new PrivateKey($this, $scalar, $compressed);
68
    }
69
70
    /**
71
     * @param PointInterface $point
72
     * @param bool|false $compressed
73
     * @return PublicKey
74 105
     */
75
    public function getPublicKey(PointInterface $point, $compressed = false)
76 105
    {
77
        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 47
     */
96
    private function doVerify(BufferInterface $messageHash, PublicKey $publicKey, Signature $signature)
97 47
    {
98 47
        $hash = gmp_init($messageHash->getHex(), 16);
99 47
        $signer = new Signer($this->math);
100
        return $signer->verify($publicKey, $signature, $hash);
101 47
    }
102 47
103 47
    /**
104 47
     * @param BufferInterface $messageHash
105 3
     * @param PublicKeyInterface $publicKey
106
     * @param SignatureInterface $signature
107
     * @return bool
108 44
     */
109
    public function verify(BufferInterface $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
110
    {
111
        /** @var PublicKey $publicKey */
112 44
        /** @var Signature $signature */
113 44
        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 44
    }
115 44
116 44
    /**
117
     * @param BufferInterface $messageHash
118 44
     * @param PrivateKey $privateKey
119
     * @param RbgInterface|null $rbg
120
     * @return Signature
121
     */
122
    private function doSign(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
123
    {
124
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
125
        $randomK = gmp_init($rbg->bytes(32)->getHex(), 16);
126
        $hash = gmp_init($messageHash->getHex(), 16);
127 47
128
        $signer = new Signer($this->math);
129
        $signature = $signer->sign($privateKey, $hash, $randomK);
130
        $s = $signature->getS();
131 47
132
        // if s is less than half the curve order, invert s
133
        if (!$this->validateSignatureElement($s, true)) {
134
            $s = $this->math->sub($this->generator->getOrder(), $s);
135
        }
136
137
        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 46
    /**
141
     * @param BufferInterface $messageHash
142 46
     * @param PrivateKeyInterface $privateKey
143 46
     * @param RbgInterface $rbg
144
     * @return SignatureInterface
145 46
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
146 46
     */
147 46
    public function sign(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
148
    {
149 46
        /** @var PrivateKey $privateKey */
150 46
        return $this->doSign($messageHash, $privateKey, $rbg);
151
    }
152 46
153
    /**
154
     * @param BufferInterface $messageHash
155
     * @param CompactSignatureInterface $signature
156 46
     * @return PublicKey
157 46
     * @throws \Exception
158 46
     */
159 46
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature)
160 46
    {
161 46
        $math = $this->getMath();
162 46
        $G = $this->getGenerator();
163 46
164
        $zero = gmp_init(0);
165 23
        $one = gmp_init(1);
166 23
167
        $r = $signature->getR();
168 23
        $s = $signature->getS();
169 23
        $recGMP = gmp_init($signature->getRecoveryId(), 10);
170
        $isYEven = $math->cmp($math->bitwiseAnd($recGMP, $one), $zero) !== 0;
171 23
        $isSecondKey = $math->cmp($math->bitwiseAnd($recGMP, gmp_init(2)), $zero) !== 0;
172
        $curve = $G->getCurve();
173 46
174
        // Precalculate (p + 1) / 4 where p is the field order
175
        $p_over_four = $math->div($math->add($curve->getPrime(), $one), gmp_init(4));
176
177
        // 1.1 Compute x
178 46
        if (!$isSecondKey) {
179 26
            $x = $r;
180 12
        } else {
181
            $x = $math->add($r, $G->getOrder());
182 46
        }
183
184
        // 1.3 Convert x to point
185
        $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
        $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
        if ($math->isEven($beta) === $isYEven) {
191
            $y = $math->sub($curve->getPrime(), $beta);
192 46
        } else {
193
            $y = $beta;
194
        }
195 46
196
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
197
        $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
        $point_negate = function (PointInterface $p) use ($math, $G) {
200
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), gmp_init('-1', 10)));
201
        };
202
203
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
204 17
        $rInv = $math->inverseMod($r, $G->getOrder());
205
        $eGNeg = $point_negate($G->mul(gmp_init($messageHash->getInt())));
206 17
        $Q = $R->mul($s)->add($eGNeg)->mul($rInv);
207 17
208
        // 1.6.2 Test Q as a public key
209 17
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
210 17
        if ($this->verify($messageHash, $Qk, $signature->convert())) {
211
            return $Qk;
212 17
        }
213 17
214 17
        throw new \Exception('Unable to recover public key');
215 17
    }
216 17
217 17
    /**
218
     * Attempt to calculate the public key recovery param by trial and error
219
     *
220 17
     * @param \GMP $r
221
     * @param \GMP $s
222
     * @param BufferInterface $messageHash
223 17
     * @param PublicKey $publicKey
224 17
     * @return int
225 9
     * @throws \Exception
226 3
     */
227
    public function calcPubKeyRecoveryParam(\GMP $r, \GMP $s, BufferInterface $messageHash, PublicKey $publicKey)
228
    {
229
        $Q = $publicKey->getPoint();
230 17
        for ($i = 0; $i < 4; $i++) {
231 17
            try {
232
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
233
                if ($Q->equals($recover->getPoint())) {
234
                    return $i;
235 17
                }
236 13
            } catch (\Exception $e) {
237 6
                continue;
238 13
            }
239
        }
240
241
        throw new \Exception('Failed to find valid recovery factor');
242 17
    }
243
244 17
    /**
245 17
     * @param BufferInterface $messageHash
246 17
     * @param PrivateKey $privateKey
247
     * @param RbgInterface|null $rbg
248
     * @return CompactSignature
249 17
     * @throws \Exception
250 17
     */
251 17
    private function doSignCompact(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
252
    {
253
        $sign = $this->sign($messageHash, $privateKey, $rbg);
254 17
255 17
        // calculate the recovery param
256 14
        // there should be a way to get this when signing too, but idk how ...
257
        return new CompactSignature(
258
            $this,
259 3
            $sign->getR(),
260
            $sign->getS(),
261
            $this->calcPubKeyRecoveryParam($sign->getR(), $sign->getS(), $messageHash, $privateKey->getPublicKey()),
262
            $privateKey->isCompressed()
263
        );
264
    }
265
266
    /**
267
     * @param PrivateKeyInterface $privateKey
268
     * @param BufferInterface $messageHash
269
     * @param RbgInterface $rbg
270
     * @return CompactSignature
271
     */
272 15
    public function signCompact(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
273
    {
274 15
        /** @var PrivateKey $privateKey */
275 15
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
276
    }
277 15
278 12
    /**
279 12
     * @param BufferInterface $privateKey
280
     * @return bool
281 5
     */
282 3
    public function validatePrivateKey(BufferInterface $privateKey)
283
    {
284 2
        $math = $this->math;
285
        $scalar = gmp_init($privateKey->getInt(), 10);
286 3
        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
    public function validateSignatureElement(\GMP $element, $half = false)
295
    {
296 12
        $math = $this->getMath();
297
        $against = $this->getGenerator()->getOrder();
298 12
        if ($half) {
299
            $against = $math->rightShift($against, 1);
300
        }
301
302 12
        return $math->cmp($element, $against) < 0 && $math->cmp($element, gmp_init(0)) !== 0;
303 6
    }
304 12
305 12
    /**
306 12
     * @param BufferInterface $publicKey
307 12
     * @return PublicKeyInterface
308 6
     * @throws \Exception
309
     */
310
    public function publicKeyFromBuffer(BufferInterface $publicKey)
311
    {
312
        $prefix = $publicKey->slice(0, 1)->getBinary();
313
        $size = $publicKey->getSize();
314
        if ($prefix == PublicKey::KEY_UNCOMPRESSED) {
315
            if ($size !== PublicKey::LENGTH_UNCOMPRESSED) {
316
                throw new \Exception('Invalid length for uncompressed key');
317 12
            }
318
            $compressed = false;
319
        } else if ($prefix === PublicKey::KEY_COMPRESSED_EVEN || $prefix === PublicKey::KEY_COMPRESSED_ODD) {
320 12
            if ($size !== PublicKey::LENGTH_COMPRESSED) {
321
                throw new \Exception('Invalid length for compressed key');
322
            }
323
            $compressed = true;
324
        } else {
325
            throw new \Exception('Unknown public key prefix');
326
        }
327 129
328
        $x = gmp_init($publicKey->slice(1, 32)->getInt(), 10);
329 129
        $curve = $this->generator->getCurve();
330 129
        $y = $compressed
331 129
            ? $curve->recoverYfromX($prefix === PublicKey::KEY_COMPRESSED_ODD, $x)
332
            : gmp_init($publicKey->slice(33, 32)->getInt(), 10);
333
334
        return new PublicKey(
335
            $this,
336
            $curve->getPoint($x, $y),
337
            $compressed
338
        );
339 50
    }
340
}
341