Completed
Push — master ( 8782bc...7383d7 )
by thomas
27:08
created

EcAdapter::publicKeyFromBuffer()   B

Complexity

Conditions 9
Paths 7

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 9.0076

Importance

Changes 0
Metric Value
cc 9
nc 7
nop 1
dl 0
loc 31
ccs 21
cts 22
cp 0.9545
crap 9.0076
rs 8.0555
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Adapter;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PrivateKey;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Signature\CompactSignature;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
12
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
13
use BitWasp\Bitcoin\Crypto\EcAdapter\Signature\CompactSignatureInterface;
14
use BitWasp\Bitcoin\Math\Math;
15
use BitWasp\Buffertools\BufferInterface;
16
use Mdanter\Ecc\Primitives\GeneratorPoint;
17
use Mdanter\Ecc\Primitives\PointInterface;
18
19
class EcAdapter implements EcAdapterInterface
20
{
21
    /**
22
     * @var Math
23
     */
24
    private $math;
25
26
    /**
27
     * @var GeneratorPoint
28
     */
29
    private $generator;
30
31
    /**
32
     * @param Math $math
33
     * @param GeneratorPoint $generator
34
     */
35 4
    public function __construct(Math $math, GeneratorPoint $generator)
36
    {
37 4
        $this->math = $math;
38 4
        $this->generator = $generator;
39 4
    }
40
41
    /**
42
     * @return Math
43
     */
44 1385
    public function getMath(): Math
45
    {
46 1385
        return $this->math;
47
    }
48
49
    /**
50
     * @return GeneratorPoint
51
     */
52 255
    public function getGenerator()
53
    {
54 255
        return $this->generator;
55
    }
56
57
    /**
58
     * @return \GMP
59
     */
60 157
    public function getOrder(): \GMP
61
    {
62 157
        return $this->generator->getOrder();
63
    }
64
65
    /**
66
     * @param \GMP $scalar
67
     * @param bool|false $compressed
68
     * @return PrivateKeyInterface
69
     */
70 92
    public function getPrivateKey(\GMP $scalar, bool $compressed = false): PrivateKeyInterface
71
    {
72 92
        return new PrivateKey($this, $scalar, $compressed);
73
    }
74
75
    /**
76
     * @param BufferInterface $messageHash
77
     * @param CompactSignature|CompactSignatureInterface $signature
78
     * @return PublicKeyInterface
79
     * @throws \Exception
80
     */
81 8
    public function recover(BufferInterface $messageHash, CompactSignatureInterface $signature): PublicKeyInterface
82
    {
83 8
        $math = $this->getMath();
84 8
        $G = $this->generator;
85
86 8
        $one = gmp_init(1);
87
88 8
        $r = $signature->getR();
89 8
        $s = $signature->getS();
90 8
        $isYEven = ($signature->getRecoveryId() & 1) !== 0;
91 8
        $isSecondKey = ($signature->getRecoveryId() & 2) !== 0;
92 8
        $curve = $G->getCurve();
93
94
        // Precalculate (p + 1) / 4 where p is the field order
95 8
        $pOverFour = $math->div($math->add($curve->getPrime(), $one), gmp_init(4));
96
97
        // 1.1 Compute x
98 8
        if (!$isSecondKey) {
99 8
            $x = $r;
100
        } else {
101 1
            $x = $math->add($r, $G->getOrder());
102
        }
103
104
        // 1.3 Convert x to point
105 8
        $alpha = $math->mod($math->add($math->add($math->pow($x, 3), $math->mul($curve->getA(), $x)), $curve->getB()), $curve->getPrime());
106 8
        $beta = $math->powmod($alpha, $pOverFour, $curve->getPrime());
107
108
        // If beta is even, but y isn't or vice versa, then convert it,
109
        // otherwise we're done and y=beta.
110 8
        if ($math->isEven($beta) === $isYEven) {
111 3
            $y = $math->sub($curve->getPrime(), $beta);
112
        } else {
113 6
            $y = $beta;
114
        }
115
116
        // 1.4 Check that nR is at infinity (implicitly done in constructor)
117 8
        $R = $G->getCurve()->getPoint($x, $y);
118
119 8
        $pointNegate = function (PointInterface $p) use ($math, $G) {
120 8
            return $G->getCurve()->getPoint($p->getX(), $math->mul($p->getY(), gmp_init('-1', 10)));
121 8
        };
122
123
        // 1.6.1 Compute a candidate public key Q = r^-1 (sR - eG)
124 8
        $rInv = $math->inverseMod($r, $G->getOrder());
125 8
        $eGNeg = $pointNegate($G->mul($messageHash->getGmp()));
126 8
        $Q = $R->mul($s)->add($eGNeg)->mul($rInv);
127
128
        // 1.6.2 Test Q as a public key
129 8
        $Qk = new PublicKey($this, $Q, $signature->isCompressed());
130 8
        if ($Qk->verify($messageHash, $signature->convert())) {
131 7
            return $Qk;
132
        }
133
134 1
        throw new \Exception('Unable to recover public key');
135
    }
136
137
    /**
138
     * Attempt to calculate the public key recovery param by trial and error
139
     *
140
     * @param \GMP $r
141
     * @param \GMP $s
142
     * @param BufferInterface $messageHash
143
     * @param PublicKey $publicKey
144
     * @return int
145
     * @throws \Exception
146
     */
147 6
    public function calcPubKeyRecoveryParam(\GMP $r, \GMP $s, BufferInterface $messageHash, PublicKey $publicKey): int
148
    {
149 6
        $Q = $publicKey->getPoint();
150 6
        for ($i = 0; $i < 4; $i++) {
151
            try {
152 6
                $recover = $this->recover($messageHash, new CompactSignature($this, $r, $s, $i, $publicKey->isCompressed()));
153 5
                if ($Q->equals($recover->getPoint())) {
154 5
                    return $i;
155
                }
156 1
            } catch (\Exception $e) {
157 1
                continue;
158
            }
159
        }
160
161 1
        throw new \Exception('Failed to find valid recovery factor');
162
    }
163
164
    /**
165
     * @param BufferInterface $privateKey
166
     * @return bool
167
     */
168 94
    public function validatePrivateKey(BufferInterface $privateKey): bool
169
    {
170 94
        $math = $this->math;
171 94
        $scalar = $privateKey->getGmp();
172 94
        return $math->cmp($scalar, gmp_init(0)) > 0 && $math->cmp($scalar, $this->getOrder()) < 0;
173
    }
174
175
    /**
176
     * @param \GMP $element
177
     * @param bool $half
178
     * @return bool
179
     */
180 78
    public function validateSignatureElement(\GMP $element, bool $half = false): bool
181
    {
182 78
        $math = $this->getMath();
183 78
        $against = $this->getOrder();
184 78
        if ($half) {
185 78
            $against = $math->rightShift($against, 1);
186
        }
187
188 78
        return $math->cmp($element, $against) < 0 && $math->cmp($element, gmp_init(0)) !== 0;
189
    }
190
191
    /**
192
     * @param BufferInterface $publicKey
193
     * @return PublicKeyInterface
194
     * @throws \Exception
195
     */
196 265
    public function publicKeyFromBuffer(BufferInterface $publicKey): PublicKeyInterface
197
    {
198 265
        $prefix = $publicKey->slice(0, 1)->getBinary();
199 265
        $size = $publicKey->getSize();
200 265
        $compressed = false;
201 265
        if ($prefix === PublicKey::KEY_UNCOMPRESSED || $prefix === "\x06" || $prefix === "\x07") {
202 95
            if ($size !== PublicKey::LENGTH_UNCOMPRESSED) {
203 95
                throw new \Exception('Invalid length for uncompressed key');
204
            }
205 179
        } else if ($prefix === PublicKey::KEY_COMPRESSED_EVEN || $prefix === PublicKey::KEY_COMPRESSED_ODD) {
206 178
            if ($size !== PublicKey::LENGTH_COMPRESSED) {
207
                throw new \Exception('Invalid length for compressed key');
208
            }
209 178
            $compressed = true;
210
        } else {
211 1
            throw new \Exception('Unknown public key prefix');
212
        }
213
        
214 263
        $x = $publicKey->slice(1, 32)->getGmp();
215 263
        $curve = $this->generator->getCurve();
216 263
        $y = $compressed
217 178
            ? $curve->recoverYfromX($prefix === PublicKey::KEY_COMPRESSED_ODD, $x)
218 263
            : $publicKey->slice(33, 32)->getGmp();
219
220 263
        return new PublicKey(
221 263
            $this,
222 263
            $curve->getPoint($x, $y),
223 263
            $compressed,
224 263
            $prefix
225
        );
226
    }
227
}
228