Completed
Push — master ( eabe8c...61f277 )
by thomas
25:26
created

PrivateKey::__construct()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 0
Metric Value
cc 3
eloc 9
nc 3
nop 3
dl 0
loc 15
ccs 9
cts 10
cp 0.9
crap 3.009
rs 9.9666
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Key;
6
7
use BitWasp\Bitcoin\Bitcoin;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Adapter\EcAdapter;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Serializer\Key\PrivateKeySerializer;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Signature\CompactSignature;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Signature\Signature;
12
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\Key;
13
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\KeyInterface;
14
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
15
use BitWasp\Bitcoin\Crypto\EcAdapter\Signature\CompactSignatureInterface;
16
use BitWasp\Bitcoin\Crypto\Random\RbgInterface;
17
use BitWasp\Bitcoin\Exceptions\InvalidPrivateKey;
18
use BitWasp\Bitcoin\Network\NetworkInterface;
19
use BitWasp\Bitcoin\Serializer\Key\PrivateKey\WifPrivateKeySerializer;
20
use BitWasp\Buffertools\Buffer;
21
use BitWasp\Buffertools\BufferInterface;
22
23
class PrivateKey extends Key implements PrivateKeyInterface
24
{
25
    /**
26
     * @var \GMP
27
     */
28
    private $secret;
29
30
    /**
31
     * @var string
32
     */
33
    private $secretBin;
34
35
    /**
36
     * @var bool
37
     */
38
    private $compressed;
39
40
    /**
41
     * @var PublicKey
42
     */
43
    private $publicKey;
44
45
    /**
46
     * @var EcAdapter
47
     */
48
    private $ecAdapter;
49
50
    /**
51
     * @param EcAdapter $adapter
52
     * @param \GMP $secret
53
     * @param bool|false $compressed
54
     * @throws \Exception
55
     */
56 50
    public function __construct(EcAdapter $adapter, \GMP $secret, bool $compressed = false)
57
    {
58 50
        $buffer = Buffer::int(gmp_strval($secret, 10), 32);
59 50
        if (!$adapter->validatePrivateKey($buffer)) {
60 1
            throw new InvalidPrivateKey('Invalid private key');
61
        }
62
63 49
        if (false === is_bool($compressed)) {
0 ignored issues
show
introduced by
The condition false === is_bool($compressed) is always false.
Loading history...
64
            throw new \InvalidArgumentException('PrivateKey: Compressed argument must be a boolean');
65
        }
66
67 49
        $this->ecAdapter = $adapter;
68 49
        $this->secret = $secret;
69 49
        $this->secretBin = $buffer->getBinary();
70 49
        $this->compressed = $compressed;
71 49
    }
72
73
    /**
74
     * @param BufferInterface $msg32
75
     * @param RbgInterface|null $rbgInterface
76
     * @return Signature
77
     */
78 32
    public function sign(BufferInterface $msg32, RbgInterface $rbgInterface = null): Signature
79
    {
80 32
        $context = $this->ecAdapter->getContext();
81
82 32
        $sig_t = null;
83 32
        if (1 !== secp256k1_ecdsa_sign($context, $sig_t, $msg32->getBinary(), $this->secretBin)) {
84
            throw new \RuntimeException('Secp256k1: failed to sign');
85
        }
86
        /** @var resource $sig_t */
87 32
        $derSig = '';
88 32
        secp256k1_ecdsa_signature_serialize_der($context, $derSig, $sig_t);
89
90 32
        $rL = ord($derSig[3]);
91 32
        $r = (new Buffer(substr($derSig, 4, $rL), $rL))->getGmp();
92
93 32
        $sL = ord($derSig[4+$rL + 1]);
94 32
        $s = (new Buffer(substr($derSig, 4 + $rL + 2, $sL), $sL))->getGmp();
95
96 32
        return new Signature($this->ecAdapter, $r, $s, $sig_t);
97
    }
98
99
    /**
100
     * @param BufferInterface $msg32
101
     * @param RbgInterface|null $rbfInterface
102
     * @return CompactSignature
103
     */
104 5
    public function signCompact(BufferInterface $msg32, RbgInterface $rbfInterface = null): CompactSignatureInterface
105
    {
106 5
        $context = $this->ecAdapter->getContext();
107
        
108 5
        $sig_t = null;
109 5
        if (1 !== secp256k1_ecdsa_sign_recoverable($context, $sig_t, $msg32->getBinary(), $this->secretBin)) {
110
            throw new \RuntimeException('Secp256k1: failed to sign');
111
        }
112
        /** @var resource $sig_t
113
         */
114 5
        $recid = 0;
115 5
        $ser = '';
116 5
        if (!secp256k1_ecdsa_recoverable_signature_serialize_compact($context, $ser, $recid, $sig_t)) {
117
            throw new \RuntimeException('Failed to obtain recid');
118
        }
119
120
        /** @var resource $sig_t */
121
        /** @var int $recid */
122
123 5
        unset($ser);
124 5
        return new CompactSignature(
0 ignored issues
show
Bug Best Practice introduced by
The expression return new BitWasp\Bitco... $this->isCompressed()) returns the type BitWasp\Bitcoin\Crypto\E...nature\CompactSignature which is incompatible with the return type mandated by BitWasp\Bitcoin\Crypto\E...nterface::signCompact() of BitWasp\Bitcoin\Crypto\E...nature\CompactSignature.

In the issue above, the returned value is violating the contract defined by the mentioned interface.

Let's take a look at an example:

interface HasName {
    /** @return string */
    public function getName();
}

class Name {
    public $name;
}

class User implements HasName {
    /** @return string|Name */
    public function getName() {
        return new Name('foo'); // This is a violation of the ``HasName`` interface
                                // which only allows a string value to be returned.
    }
}
Loading history...
125 5
            $this->ecAdapter,
126 5
            $sig_t,
127 5
            $recid,
128 5
            $this->isCompressed()
129
        );
130
    }
131
132
    /**
133
     * @return bool
134
     */
135 46
    public function isCompressed(): bool
136
    {
137 46
        return $this->compressed;
138
    }
139
140
    /**
141
     * @return \GMP
142
     */
143 3
    public function getSecret()
144
    {
145 3
        return $this->secret;
146
    }
147
148
    /**
149
     * @return string
150
     */
151 70
    public function getSecretBinary(): string
152
    {
153 70
        return $this->secretBin;
154
    }
155
156
    /**
157
     * @return PublicKey
158
     */
159 65
    public function getPublicKey()
160
    {
161 65
        if (null === $this->publicKey) {
162 65
            $context = $this->ecAdapter->getContext();
163 65
            $publicKey_t = null;
164 65
            if (1 !== secp256k1_ec_pubkey_create($context, $publicKey_t, $this->getBinary())) {
165
                throw new \RuntimeException('Failed to create public key');
166
            }
167
            /** @var resource $publicKey_t */
168 65
            $this->publicKey = new PublicKey($this->ecAdapter, $publicKey_t, $this->compressed);
169
        }
170
171 65
        return $this->publicKey;
172
    }
173
174
    /**
175
     * @param \GMP $tweak
176
     * @return KeyInterface
177
     */
178 17
    public function tweakAdd(\GMP $tweak): KeyInterface
179
    {
180 17
        $adapter = $this->ecAdapter;
181 17
        $math = $adapter->getMath();
182 17
        $context = $adapter->getContext();
183 17
        $privateKey = $this->getBinary(); // mod by reference
184 17
        $tweak = Buffer::int($math->toString($tweak), 32)->getBinary();
185 17
        $ret = \secp256k1_ec_privkey_tweak_add(
186 17
            $context,
187 17
            $privateKey,
188 17
            $tweak
189
        );
190
191 17
        if ($ret !== 1) {
192
            throw new \RuntimeException('Secp256k1 privkey tweak add: failed');
193
        }
194
195 17
        $secret = new Buffer($privateKey);
196 17
        return $adapter->getPrivateKey($secret->getGmp(), $this->compressed);
197
    }
198
199
    /**
200
     * @param \GMP $tweak
201
     * @return KeyInterface
202
     */
203 1
    public function tweakMul(\GMP $tweak): KeyInterface
204
    {
205 1
        $privateKey = $this->getBinary();
206 1
        $math = $this->ecAdapter->getMath();
207 1
        $tweak = Buffer::int($math->toString($tweak), 32)->getBinary();
208 1
        $ret = \secp256k1_ec_privkey_tweak_mul(
209 1
            $this->ecAdapter->getContext(),
210 1
            $privateKey,
211 1
            $tweak
212
        );
213
214 1
        if ($ret !== 1) {
215
            throw new \RuntimeException('Secp256k1 privkey tweak mul: failed');
216
        }
217
218 1
        $secret = new Buffer($privateKey);
219
220 1
        return $this->ecAdapter->getPrivateKey($secret->getGmp(), $this->compressed);
221
    }
222
223
    /**
224
     * @param NetworkInterface $network
225
     * @return string
226
     */
227 4
    public function toWif(NetworkInterface $network = null): string
228
    {
229 4
        $network = $network ?: Bitcoin::getNetwork();
230 4
        $wifSerializer = new WifPrivateKeySerializer(new PrivateKeySerializer($this->ecAdapter));
231 4
        return $wifSerializer->serialize($network, $this);
232
    }
233
234
    /**
235
     * @return BufferInterface
236
     */
237 68
    public function getBuffer(): BufferInterface
238
    {
239 68
        return (new PrivateKeySerializer($this->ecAdapter))->serialize($this);
240
    }
241
}
242