Completed
Pull Request — master (#375)
by thomas
71:49
created

EcAdapter::doEcdh()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 7
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 2
ccs 0
cts 0
cp 0
crap 2
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\Hash;
13
use BitWasp\Bitcoin\Crypto\Random\RbgInterface;
14
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
15
use BitWasp\Bitcoin\Math\Math;
16
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PrivateKey;
17
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Signature\CompactSignature;
18
use BitWasp\Buffertools\Buffer;
19
use BitWasp\Buffertools\BufferInterface;
20
use Mdanter\Ecc\Crypto\Signature\Signer;
21
use Mdanter\Ecc\Primitives\GeneratorPoint;
22
use Mdanter\Ecc\Primitives\PointInterface;
23
24
class EcAdapter implements EcAdapterInterface
25
{
26
    /**
27
     * @var Math
28
     */
29
    private $math;
30
31
    /**
32
     * @var GeneratorPoint
33
     */
34
    private $generator;
35
36
    /**
37
     * @param Math $math
38 28
     * @param GeneratorPoint $generator
39
     */
40 28
    public function __construct(Math $math, GeneratorPoint $generator)
41 28
    {
42 28
        $this->math = $math;
43
        $this->generator = $generator;
44
    }
45
46
    /**
47 378
     * @return Math
48
     */
49 378
    public function getMath()
50
    {
51
        return $this->math;
52
    }
53
54
    /**
55 270
     * @return GeneratorPoint
56
     */
57 270
    public function getGenerator()
58
    {
59
        return $this->generator;
60
    }
61
62
    /**
63
     * @param \GMP $scalar
64
     * @param bool|false $compressed
65 104
     * @return PrivateKey
66
     */
67 104
    public function getPrivateKey(\GMP $scalar, $compressed = false)
68
    {
69
        return new PrivateKey($this, $scalar, $compressed);
70
    }
71
72
    /**
73
     * @param PointInterface $point
74
     * @param bool|false $compressed
75 88
     * @return PublicKey
76
     */
77 88
    public function getPublicKey(PointInterface $point, $compressed = false)
78
    {
79
        return new PublicKey($this, $point, $compressed);
80
    }
81
82
    /**
83
     * @param \GMP $r
84
     * @param \GMP $s
85
     * @return Signature
86
     */
87
    public function getSignature(\GMP $r, \GMP $s)
88
    {
89
        return new Signature($this, $r, $s);
90
    }
91
92
    /**
93
     * @param BufferInterface $messageHash
94
     * @param PublicKey $publicKey
95
     * @param Signature $signature
96 157
     * @return bool
97
     */
98 157
    private function doVerify(BufferInterface $messageHash, PublicKey $publicKey, Signature $signature)
99 157
    {
100 157
        $hash = gmp_init($messageHash->getHex(), 16);
101
        $signer = new Signer($this->math);
102
        return $signer->verify($publicKey, $signature, $hash);
103
    }
104
105
    /**
106
     * @param BufferInterface $messageHash
107
     * @param PublicKeyInterface $publicKey
108
     * @param SignatureInterface $signature
109 157
     * @return bool
110
     */
111
    public function verify(BufferInterface $messageHash, PublicKeyInterface $publicKey, SignatureInterface $signature)
112
    {
113 157
        /** @var PublicKey $publicKey */
114
        /** @var Signature $signature */
115
        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...
116
    }
117
118
    /**
119
     * @param BufferInterface $messageHash
120
     * @param PrivateKey $privateKey
121
     * @param RbgInterface|null $rbg
122 46
     * @return Signature
123
     */
124 46
    private function doSign(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
125 46
    {
126 46
        $rbg = $rbg ?: new Rfc6979($this, $privateKey, $messageHash);
127
        $randomK = gmp_init($rbg->bytes(32)->getHex(), 16);
128 46
        $hash = gmp_init($messageHash->getHex(), 16);
129 46
130 46
        $signer = new Signer($this->math);
131
        $signature = $signer->sign($privateKey, $hash, $randomK);
132
        $s = $signature->getS();
133 46
134 23
        // if s is less than half the curve order, invert s
135
        if (!$this->validateSignatureElement($s, true)) {
136
            $s = $this->math->sub($this->generator->getOrder(), $s);
137 46
        }
138
139
        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 136 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...
140
    }
141
142
    /**
143
     * @param BufferInterface $messageHash
144
     * @param PrivateKeyInterface $privateKey
145
     * @param RbgInterface $rbg
146
     * @return SignatureInterface
147 46
     * @throws \BitWasp\Bitcoin\Exceptions\RandomBytesFailure
148
     */
149
    public function sign(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
150 46
    {
151
        /** @var PrivateKey $privateKey */
152
        return $this->doSign($messageHash, $privateKey, $rbg);
153
    }
154
155
    /**
156
     * @param BufferInterface $messageHash
157
     * @param CompactSignatureInterface $signature
158
     * @return PublicKey
159 20
     * @throws \Exception
160
     */
161 20
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature)
162 20
    {
163
        $math = $this->getMath();
164 20
        $G = $this->getGenerator();
165 20
166
        $zero = gmp_init(0);
167 20
        $one = gmp_init(1);
168 20
169 20
        $r = $signature->getR();
170 20
        $s = $signature->getS();
171 20
        $recGMP = gmp_init($signature->getRecoveryId(), 10);
172 20
        $isYEven = $math->cmp($math->bitwiseAnd($recGMP, $one), $zero) !== 0;
173
        $isSecondKey = $math->cmp($math->bitwiseAnd($recGMP, gmp_init(2)), $zero) !== 0;
174
        $curve = $G->getCurve();
175 20
176
        // Precalculate (p + 1) / 4 where p is the field order
177
        $p_over_four = $math->div($math->add($curve->getPrime(), $one), gmp_init(4));
178 20
179 20
        // 1.1 Compute x
180 2
        if (!$isSecondKey) {
181 6
            $x = $r;
182
        } else {
183
            $x = $math->add($r, $G->getOrder());
184
        }
185 20
186 20
        // 1.3 Convert x to point
187
        $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 183 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 183 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...
188
        $beta = $math->powmod($alpha, $p_over_four, $curve->getPrime());
189
190 20
        // If beta is even, but y isn't or vice versa, then convert it,
191 16
        // otherwise we're done and y == beta.
192 2
        if ($math->isEven($beta) === $isYEven) {
193 10
            $y = $math->sub($curve->getPrime(), $beta);
194
        } else {
195
            $y = $beta;
196
        }
197 20
198
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
199 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 183 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...
200 20
201 20
        $point_negate = function (PointInterface $p) use ($math, $G) {
202
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), gmp_init('-1', 10)));
203
        };
204 20
205 20
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
206 20
        $rInv = $math->inverseMod($r, $G->getOrder());
207
        $eGNeg = $point_negate($G->mul($messageHash->getGmp()));
208
        $Q = $R->mul($s)->add($eGNeg)->mul($rInv);
209 20
210 20
        // 1.6.2 Test Q as a public key
211 14
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
212
        if ($this->verify($messageHash, $Qk, $signature->convert())) {
213
            return $Qk;
214 6
        }
215
216
        throw new \Exception('Unable to recover public key');
217
    }
218
219
    /**
220
     * Attempt to calculate the public key recovery param by trial and error
221
     *
222
     * @param \GMP $r
223
     * @param \GMP $s
224
     * @param BufferInterface $messageHash
225
     * @param PublicKey $publicKey
226
     * @return int
227 18
     * @throws \Exception
228
     */
229 18
    public function calcPubKeyRecoveryParam(\GMP $r, \GMP $s, BufferInterface $messageHash, PublicKey $publicKey)
230 18
    {
231
        $Q = $publicKey->getPoint();
232 18
        for ($i = 0; $i < 4; $i++) {
233 12
            try {
234 12
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
235
                if ($Q->equals($recover->getPoint())) {
236 6
                    return $i;
237 6
                }
238
            } catch (\Exception $e) {
239
                continue;
240
            }
241 6
        }
242
243
        throw new \Exception('Failed to find valid recovery factor');
244
    }
245
246
    /**
247
     * @param BufferInterface $messageHash
248
     * @param PrivateKey $privateKey
249
     * @param RbgInterface|null $rbg
250
     * @return CompactSignature
251 12
     * @throws \Exception
252
     */
253 12
    private function doSignCompact(BufferInterface $messageHash, PrivateKey $privateKey, RbgInterface $rbg = null)
254
    {
255
        $sign = $this->sign($messageHash, $privateKey, $rbg);
256
257 12
        // calculate the recovery param
258
        // there should be a way to get this when signing too, but idk how ...
259 12
        return new CompactSignature(
260 12
            $this,
261 12
            $sign->getR(),
262 12
            $sign->getS(),
263
            $this->calcPubKeyRecoveryParam($sign->getR(), $sign->getS(), $messageHash, $privateKey->getPublicKey()),
264
            $privateKey->isCompressed()
265
        );
266
    }
267
268
    /**
269
     * @param PrivateKeyInterface $privateKey
270
     * @param BufferInterface $messageHash
271
     * @param RbgInterface $rbg
272 12
     * @return CompactSignature
273
     */
274
    public function signCompact(BufferInterface $messageHash, PrivateKeyInterface $privateKey, RbgInterface $rbg = null)
275 12
    {
276
        /** @var PrivateKey $privateKey */
277
        return $this->doSignCompact($messageHash, $privateKey, $rbg);
278
    }
279
280
    /**
281
     * @param BufferInterface $privateKey
282 108
     * @return bool
283
     */
284 108
    public function validatePrivateKey(BufferInterface $privateKey)
285 108
    {
286 108
        $math = $this->math;
287
        $scalar = $privateKey->getGmp();
288
        return $math->cmp($scalar, gmp_init(0)) > 0 && $math->cmp($scalar, $this->getGenerator()->getOrder()) < 0;
289
    }
290
291
    /**
292
     * @param \GMP $element
293
     * @param bool $half
294 52
     * @return bool
295
     */
296 52
    public function validateSignatureElement(\GMP $element, $half = false)
297 52
    {
298 52
        $math = $this->getMath();
299 52
        $against = $this->getGenerator()->getOrder();
300
        if ($half) {
301
            $against = $math->rightShift($against, 1);
302 52
        }
303
304
        return $math->cmp($element, $against) < 0 && $math->cmp($element, gmp_init(0)) !== 0;
305
    }
306
307
    /**
308
     * @param BufferInterface $publicKey
309
     * @return PublicKeyInterface
310 272
     * @throws \Exception
311
     */
312 272
    public function publicKeyFromBuffer(BufferInterface $publicKey)
313 272
    {
314 272
        $prefix = $publicKey->slice(0, 1)->getBinary();
315 272
        $size = $publicKey->getSize();
316 134
        $compressed = false;
317 134
        if ($prefix == PublicKey::KEY_UNCOMPRESSED || $prefix === "\x06" || $prefix === "\x07") {
318
            if ($size !== PublicKey::LENGTH_UNCOMPRESSED) {
319 152
                throw new \Exception('Invalid length for uncompressed key');
320 150
            }
321
        } else if ($prefix === PublicKey::KEY_COMPRESSED_EVEN || $prefix === PublicKey::KEY_COMPRESSED_ODD) {
322
            if ($size !== PublicKey::LENGTH_COMPRESSED) {
323 150
                throw new \Exception('Invalid length for compressed key');
324
            }
325 2
            $compressed = true;
326
        } else {
327
            throw new \Exception('Unknown public key prefix');
328 268
        }
329 268
        
330 268
        $x = $publicKey->slice(1, 32)->getGmp();
331 150
        $curve = $this->generator->getCurve();
332 268
        $y = $compressed
333
            ? $curve->recoverYfromX($prefix === PublicKey::KEY_COMPRESSED_ODD, $x)
334 268
            : $publicKey->slice(33, 32)->getGmp();
335
336 268
        return new PublicKey(
337
            $this,
338
            $curve->getPoint($x, $y),
339
            $compressed,
340
            $prefix
341
        );
342
    }
343
344
    /**
345
     * @param \GMP $int
346
     * @param int $byteSize
347
     * @return string
348
     */
349
    private function fixedSizeInt(\GMP $int, $byteSize)
350
    {
351
        $two = gmp_init(2);
352
        $maskShift = gmp_pow($two, 8);
353
        $mask = gmp_mul(gmp_init(255), gmp_pow($two, 256));
354
355
        $x = '';
356
        for ($i = $byteSize - 1; $i >= 0; $i--) {
357
            $mask = gmp_div($mask, $maskShift);
358
            $x .= pack('C', gmp_strval(gmp_div(gmp_and($int, $mask), gmp_pow($two, $i * 8)), 10));
359
        }
360
361
        return $x;
362
    }
363
364
    /**
365
     * @param PrivateKey $privateKey
366
     * @param PublicKey $publicKey
367
     * @return BufferInterface
368
     */
369
    private function doEcdh(PrivateKey $privateKey, PublicKey $publicKey)
370
    {
371
        $point = $publicKey->getPoint()->mul($privateKey->getSecret());
372
        $prefix = pack("C", 0x02 | gmp_cmp(gmp_mod($point->getY(), gmp_init(2)), 0));
373
374
        return Hash::sha256(new Buffer($prefix . $this->fixedSizeInt($point->getX(), 32)));
375
    }
376
377
    /**
378
     * @param PrivateKeyInterface $privateKey
379
     * @param PublicKeyInterface $publicKey
380
     * @return BufferInterface
381
     */
382
    public function ecdh(PrivateKeyInterface $privateKey, PublicKeyInterface $publicKey)
383
    {
384
        /**
385
         * @var PrivateKey $privateKey
386
         * @var PublicKey $publicKey
387
         */
388
        return $this->doEcdh($privateKey, $publicKey);
389
    }
390
}
391