Completed
Pull Request — master (#758)
by thomas
19:39
created

PrivateKey::getSecret()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
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\SchnorrSignature;
12
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Signature\SchnorrSignatureInterface;
13
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Signature\Signature;
14
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\Key;
15
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\KeyInterface;
16
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
17
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\XOnlyPublicKeyInterface;
18
use BitWasp\Bitcoin\Crypto\EcAdapter\Signature\CompactSignatureInterface;
19
use BitWasp\Bitcoin\Crypto\Random\RbgInterface;
20
use BitWasp\Bitcoin\Exceptions\InvalidPrivateKey;
21
use BitWasp\Bitcoin\Network\NetworkInterface;
22
use BitWasp\Bitcoin\Serializer\Key\PrivateKey\WifPrivateKeySerializer;
23
use BitWasp\Buffertools\Buffer;
24
use BitWasp\Buffertools\BufferInterface;
25
26
class PrivateKey extends Key implements PrivateKeyInterface
27
{
28
    /**
29
     * @var \GMP
30
     */
31
    private $secret;
32
33
    /**
34
     * @var string
35
     */
36
    private $secretBin;
37
38
    /**
39
     * @var bool
40
     */
41
    private $compressed;
42
43
    /**
44
     * @var PublicKey
45
     */
46
    private $publicKey;
47
48
    /**
49
     * @var EcAdapter
50
     */
51
    private $ecAdapter;
52
53
    /**
54
     * @param EcAdapter $adapter
55
     * @param \GMP $secret
56
     * @param bool|false $compressed
57
     * @throws \Exception
58
     */
59 89
    public function __construct(EcAdapter $adapter, \GMP $secret, bool $compressed = false)
60
    {
61 89
        $buffer = Buffer::int(gmp_strval($secret, 10), 32);
62 89
        if (!$adapter->validatePrivateKey($buffer)) {
63 1
            throw new InvalidPrivateKey('Invalid private key');
64
        }
65
66 88
        if (false === is_bool($compressed)) {
0 ignored issues
show
introduced by
The condition false === is_bool($compressed) is always false.
Loading history...
67
            throw new \InvalidArgumentException('PrivateKey: Compressed argument must be a boolean');
68
        }
69
70 88
        $this->ecAdapter = $adapter;
71 88
        $this->secret = $secret;
72 88
        $this->secretBin = $buffer->getBinary();
73 88
        $this->compressed = $compressed;
74 88
    }
75
76
    public function signSchnorr(BufferInterface $msg32): \BitWasp\Bitcoin\Crypto\EcAdapter\Signature\SchnorrSignatureInterface
77
    {
78
        $context = $this->ecAdapter->getContext();
79
80
        $schnorrSig = null;
81
        if (1 !== secp256k1_schnorrsig_sign($context, $schnorrSig, $msg32->getBinary(), $this->secretBin)) {
0 ignored issues
show
Bug introduced by
The function secp256k1_schnorrsig_sign was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

81
        if (1 !== /** @scrutinizer ignore-call */ secp256k1_schnorrsig_sign($context, $schnorrSig, $msg32->getBinary(), $this->secretBin)) {
Loading history...
82
            throw new \RuntimeException("failed to sign transaction");
83
        }
84
85
        return new SchnorrSignature($context, $schnorrSig);
86
    }
87
88
    /**
89
     * @param BufferInterface $msg32
90
     * @param RbgInterface|null $rbgInterface
91
     * @return Signature
92
     */
93 68
    public function sign(BufferInterface $msg32, RbgInterface $rbgInterface = null): Signature
94
    {
95 68
        $context = $this->ecAdapter->getContext();
96
97 68
        $sig_t = null;
98 68
        if (1 !== secp256k1_ecdsa_sign($context, $sig_t, $msg32->getBinary(), $this->secretBin)) {
99
            throw new \RuntimeException('Secp256k1: failed to sign');
100
        }
101
        /** @var resource $sig_t */
102 68
        $derSig = '';
103 68
        secp256k1_ecdsa_signature_serialize_der($context, $derSig, $sig_t);
104
105 68
        $rL = ord($derSig[3]);
106 68
        $r = (new Buffer(substr($derSig, 4, $rL), $rL))->getGmp();
107
108 68
        $sL = ord($derSig[4+$rL + 1]);
109 68
        $s = (new Buffer(substr($derSig, 4 + $rL + 2, $sL), $sL))->getGmp();
110
111 68
        return new Signature($this->ecAdapter, $r, $s, $sig_t);
112
    }
113
114
    /**
115
     * @param BufferInterface $msg32
116
     * @param RbgInterface|null $rbfInterface
117
     * @return CompactSignature
118
     */
119 5
    public function signCompact(BufferInterface $msg32, RbgInterface $rbfInterface = null): CompactSignatureInterface
120
    {
121 5
        $context = $this->ecAdapter->getContext();
122
        
123 5
        $sig_t = null;
124 5
        if (1 !== secp256k1_ecdsa_sign_recoverable($context, $sig_t, $msg32->getBinary(), $this->secretBin)) {
125
            throw new \RuntimeException('Secp256k1: failed to sign');
126
        }
127
        /** @var resource $sig_t
128
         */
129 5
        $recid = 0;
130 5
        $ser = '';
131 5
        if (!secp256k1_ecdsa_recoverable_signature_serialize_compact($context, $ser, $recid, $sig_t)) {
132
            throw new \RuntimeException('Failed to obtain recid');
133
        }
134
135
        /** @var resource $sig_t */
136
        /** @var int $recid */
137
138 5
        unset($ser);
139 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...
140 5
            $this->ecAdapter,
141 5
            $sig_t,
142 5
            $recid,
143 5
            $this->isCompressed()
144
        );
145
    }
146
147
    /**
148
     * @return bool
149
     */
150 74
    public function isCompressed(): bool
151
    {
152 74
        return $this->compressed;
153
    }
154
155
    /**
156
     * @return \GMP
157
     */
158 3
    public function getSecret()
159
    {
160 3
        return $this->secret;
161
    }
162
163
    /**
164
     * @return string
165
     */
166 100
    public function getSecretBinary(): string
167
    {
168 100
        return $this->secretBin;
169
    }
170
171
    /**
172
     * @return PublicKey
173
     */
174 125
    public function getPublicKey()
175
    {
176 125
        if (null === $this->publicKey) {
177 95
            $context = $this->ecAdapter->getContext();
178 95
            $publicKey_t = null;
179 95
            if (1 !== secp256k1_ec_pubkey_create($context, $publicKey_t, $this->getBinary())) {
180
                throw new \RuntimeException('Failed to create public key');
181
            }
182
            /** @var resource $publicKey_t */
183 95
            $this->publicKey = new PublicKey($this->ecAdapter, $publicKey_t, $this->compressed);
184
        }
185
186 125
        return $this->publicKey;
187
    }
188
189
    public function getXOnlyPublicKey(): XOnlyPublicKeyInterface
190
    {
191
        $context = $this->ecAdapter->getContext();
192
        $xonlyPubKey = null;
193
        if (1 !== secp256k1_xonly_pubkey_create($context, $xonlyPubKey, $this->getBinary())) {
0 ignored issues
show
Bug introduced by
The function secp256k1_xonly_pubkey_create was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

193
        if (1 !== /** @scrutinizer ignore-call */ secp256k1_xonly_pubkey_create($context, $xonlyPubKey, $this->getBinary())) {
Loading history...
194
            throw new \RuntimeException('Failed to create public key');
195
        }
196
        /** @var resource $xonlyPubKey */
197
        return new XOnlyPublicKey($context, $xonlyPubKey);
198
    }
199
200
    /**
201
     * @param \GMP $tweak
202
     * @return KeyInterface
203
     */
204 19
    public function tweakAdd(\GMP $tweak): KeyInterface
205
    {
206 19
        $adapter = $this->ecAdapter;
207 19
        $math = $adapter->getMath();
208 19
        $context = $adapter->getContext();
209 19
        $privateKey = $this->getBinary(); // mod by reference
210 19
        $tweak = Buffer::int($math->toString($tweak), 32)->getBinary();
211 19
        $ret = \secp256k1_ec_privkey_tweak_add(
212 19
            $context,
213 19
            $privateKey,
214 19
            $tweak
215
        );
216
217 19
        if ($ret !== 1) {
218
            throw new \RuntimeException('Secp256k1 privkey tweak add: failed');
219
        }
220
221 19
        $secret = new Buffer($privateKey);
222 19
        return $adapter->getPrivateKey($secret->getGmp(), $this->compressed);
223
    }
224
225
    /**
226
     * @param \GMP $tweak
227
     * @return KeyInterface
228
     */
229 1
    public function tweakMul(\GMP $tweak): KeyInterface
230
    {
231 1
        $privateKey = $this->getBinary();
232 1
        $math = $this->ecAdapter->getMath();
233 1
        $tweak = Buffer::int($math->toString($tweak), 32)->getBinary();
234 1
        $ret = \secp256k1_ec_privkey_tweak_mul(
235 1
            $this->ecAdapter->getContext(),
236 1
            $privateKey,
237 1
            $tweak
238
        );
239
240 1
        if ($ret !== 1) {
241
            throw new \RuntimeException('Secp256k1 privkey tweak mul: failed');
242
        }
243
244 1
        $secret = new Buffer($privateKey);
245
246 1
        return $this->ecAdapter->getPrivateKey($secret->getGmp(), $this->compressed);
247
    }
248
249
    /**
250
     * @param NetworkInterface $network
251
     * @return string
252
     */
253 5
    public function toWif(NetworkInterface $network = null): string
254
    {
255 5
        $network = $network ?: Bitcoin::getNetwork();
256 5
        $wifSerializer = new WifPrivateKeySerializer(new PrivateKeySerializer($this->ecAdapter));
257 5
        return $wifSerializer->serialize($network, $this);
258
    }
259
260
    /**
261
     * @return BufferInterface
262
     */
263 98
    public function getBuffer(): BufferInterface
264
    {
265 98
        return (new PrivateKeySerializer($this->ecAdapter))->serialize($this);
266
    }
267
}
268