Completed
Push — master ( 5580cc...6f9908 )
by thomas
20s
created

HierarchicalKey::toPublic()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
ccs 4
cts 4
cp 1
crap 2
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin\Key\Deterministic;
4
5
use BitWasp\Bitcoin\Bitcoin;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\KeyInterface;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Impl\PhpEcc\Key\PublicKey;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
10
use BitWasp\Buffertools\Buffer;
11
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
12
use BitWasp\Bitcoin\Serializer\Key\HierarchicalKey\Base58ExtendedKeySerializer;
13
use BitWasp\Bitcoin\Serializer\Key\HierarchicalKey\ExtendedKeySerializer;
14
use BitWasp\Buffertools\BufferInterface;
15
use BitWasp\Buffertools\Buffertools;
16
use BitWasp\Buffertools\Parser;
17
use BitWasp\Bitcoin\Crypto\Hash;
18
use BitWasp\Bitcoin\Network\NetworkInterface;
19
20
class HierarchicalKey
21
{
22
    /**
23
     * @var EcAdapterInterface
24
     */
25
    private $ecAdapter;
26
27
    /**
28
     * @var int
29
     */
30
    private $depth;
31
32
    /**
33
     * @var int
34
     */
35
    private $parentFingerprint;
36
37
    /**
38
     * @var int
39
     */
40
    private $sequence;
41
42
    /**
43
     * @var BufferInterface
44
     */
45
    private $chainCode;
46
47
    /**
48
     * @var PublicKeyInterface|PrivateKeyInterface
49
     */
50
    private $key;
51
52
    /**
53
     * @param EcAdapterInterface $ecAdapter
54
     * @param int $depth
55
     * @param int $parentFingerprint
56
     * @param int $sequence
57
     * @param BufferInterface $chainCode
58
     * @param KeyInterface $key
59
     * @throws \Exception
60 150
     */
61
    public function __construct(EcAdapterInterface $ecAdapter, $depth, $parentFingerprint, $sequence, BufferInterface $chainCode, KeyInterface $key)
62 150
    {
63
        if ($chainCode->getSize() !== 32) {
64
            throw new \RuntimeException('Chaincode should be 32 bytes');
65
        }
66 150
67 6
        if (!$key->isCompressed()) {
68
            throw new \InvalidArgumentException('A HierarchicalKey must always be compressed');
69
        }
70 144
71 144
        $this->ecAdapter = $ecAdapter;
72 144
        $this->depth = $depth;
73 144
        $this->sequence = $sequence;
74 144
        $this->parentFingerprint = $parentFingerprint;
75 144
        $this->chainCode = $chainCode;
76 144
        $this->key = $key;
0 ignored issues
show
Documentation Bug introduced by
It seems like $key of type object<BitWasp\Bitcoin\C...apter\Key\KeyInterface> is incompatible with the declared type object<BitWasp\Bitcoin\C...ey\PrivateKeyInterface> of property $key.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
77
    }
78
79
    /**
80
     * Return the depth of this key. This is limited to 256 sequential derivations.
81
     *
82
     * @return int
83 66
     */
84
    public function getDepth()
85 66
    {
86
        return $this->depth;
87
    }
88
89
    /**
90
     * Get the sequence number for this address. Hardened keys are
91
     * created with sequence > 0x80000000. a sequence number lower
92
     * than this can be derived with the public key.
93
     *
94
     * @return int
95 36
     */
96
    public function getSequence()
97 36
    {
98
        return $this->sequence;
99
    }
100
101
    /**
102
     * Get the fingerprint of the parent key. For master keys, this is 00000000.
103
     *
104
     * @return string
105 36
     */
106
    public function getFingerprint()
107 36
    {
108 18
        if ($this->getDepth() === 0) {
109
            return 0;
110
        }
111 36
112
        return $this->parentFingerprint;
113
    }
114
115
    /**
116
     * Return the fingerprint to be used for child keys.
117
     * @return int
118 42
     */
119
    public function getChildFingerprint()
120 42
    {
121
        return $this->getPublicKey()->getPubKeyHash()->slice(0, 4)->getInt();
122
    }
123
124
    /**
125
     * Return the chain code - a deterministic 'salt' for HMAC-SHA512
126
     * in child derivations
127
     *
128
     * @return BufferInterface
129 48
     */
130
    public function getChainCode()
131 48
    {
132
        return $this->chainCode;
133
    }
134
135
    /**
136
     * @return PrivateKeyInterface
137 78
     */
138
    public function getPrivateKey()
139 78
    {
140 72
        if ($this->key->isPrivate()) {
141
            return $this->key;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->key; of type BitWasp\Bitcoin\Crypto\E...Key\PrivateKeyInterface adds the type BitWasp\Bitcoin\Crypto\E...\Key\PublicKeyInterface to the return on line 141 which is incompatible with the return type documented by BitWasp\Bitcoin\Key\Dete...hicalKey::getPrivateKey of type BitWasp\Bitcoin\Crypto\E...Key\PrivateKeyInterface.
Loading history...
142
        }
143 6
144
        throw new \RuntimeException('Unable to get private key, not known');
145
    }
146
147
    /**
148
     * Get the public key the private key or public key.
149
     *
150
     * @return PublicKeyInterface
151 84
     */
152
    public function getPublicKey()
153 84
    {
154 54
        if ($this->isPrivate()) {
155
            return $this->getPrivateKey()->getPublicKey();
156 42
        } else {
157
            return $this->key;
0 ignored issues
show
Bug Compatibility introduced by
The expression $this->key; of type BitWasp\Bitcoin\Crypto\E...Key\PrivateKeyInterface adds the type BitWasp\Bitcoin\Crypto\E...Key\PrivateKeyInterface to the return on line 157 which is incompatible with the return type documented by BitWasp\Bitcoin\Key\Dete...chicalKey::getPublicKey of type BitWasp\Bitcoin\Crypto\E...\Key\PublicKeyInterface.
Loading history...
158 3
        }
159
    }
160
161
    /**
162
     * @return HierarchicalKey
163 24
     */
164
    public function toPublic()
165 24
    {
166 18
        if ($this->isPrivate()) {
167 9
            $this->key = $this->getPrivateKey()->getPublicKey();
168
        }
169 24
170
        return $this;
171
    }
172
173
    /**
174
     * Return whether this is a private key
175
     *
176
     * @return bool
177 96
     */
178
    public function isPrivate()
179 96
    {
180
        return $this->key->isPrivate();
181
    }
182
183
    /**
184
     * Return whether the key is hardened
185
     *
186
     * @return bool
187 6
     */
188
    public function isHardened()
189 6
    {
190
        return ($this->sequence >> 31) === 1;
191
    }
192
193
    /**
194
     * Create a buffer containing data to be hashed hashed to yield the child offset
195
     *
196
     * @param int $sequence
197
     * @return BufferInterface
198
     * @throws \Exception
199 42
     */
200
    public function getHmacSeed($sequence)
201 42
    {
202 30
        if ($sequence < 0 || $sequence > pow(2, 32) - 1) {
203 6
            throw new \InvalidArgumentException('Sequence is outside valid range, must be >= 0 && <= 2^32-1');
204
        }
205
206 24
        if (($sequence >> 31) === 1) {
207 12
            if ($this->isPrivate() === false) {
208 36
                throw new \Exception("Can't derive a hardened key without the private key");
209
            }
210
211 36
            $buffer = Buffertools::concat(new Buffer("\x00"), $this->getPrivateKey()->getBuffer());
212 36
        } else {
213 36
            $buffer = $this->getPublicKey()->getBuffer();
214
        }
215
216
        return (new Parser($buffer))
217
            ->writeBytes(4, Buffer::int($sequence, 4))
218
            ->getBuffer();
219
    }
220
221
    /**
222
     * Derive a child key
223 42
     *
224
     * @param int $sequence
225 42
     * @return HierarchicalKey
226 36
     * @throws \Exception
227 36
     */
228
    public function deriveChild($sequence)
229 36
    {
230 6
        $hash = Hash::hmac('sha512', $this->getHmacSeed($sequence), $this->getChainCode());
231
        $offset = $hash->slice(0, 32);
232
        $chain = $hash->slice(32);
233 36
234 36
        if (false === $this->ecAdapter->validatePrivateKey($offset)) {
235
            return $this->deriveChild($sequence + 1);
236 36
        }
237 36
238 36
        $key = $this->isPrivate() ? $this->getPrivateKey() : $this->getPublicKey();
239 36
        $key = $key->tweakAdd($offset->getGmp());
240 18
241 18
        return new HierarchicalKey(
242
            $this->ecAdapter,
243 18
            $this->getDepth() + 1,
244
            $this->getChildFingerprint(),
245
            $sequence,
246
            $chain,
247
            $key
248
        );
249
    }
250 18
251
    /**
252 18
     * @param array|\stdClass|\Traversable $list
253
     * @return HierarchicalKey
254
     */
255
    public function deriveFromList($list)
256 18
    {
257 18
        if (!is_array($list) && !$list instanceof \Traversable && !$list instanceof \stdClass) {
258 18
            throw new \InvalidArgumentException('List must be an array or \Traversable');
259 9
        }
260
261 18
        $key = $this;
262
        foreach ($list as $sequence) {
0 ignored issues
show
Bug introduced by
The expression $list of type array|object<Traversable>|object<stdClass> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
263
            $key = $key->deriveChild($sequence);
264
        }
265
266
        return $key;
267
    }
268
269
    /**
270
     * Decodes a BIP32 path into actual 32bit sequence numbers and derives the child key
271 18
     *
272
     * @param string $path
273 18
     * @return HierarchicalKey
274 18
     * @throws \Exception
275
     */
276
    public function derivePath($path)
277
    {
278
        $sequences = new HierarchicalKeySequence();
279
        return $this->deriveFromList($sequences->decodePath($path));
280
    }
281
282 30
    /**
283
     * Serializes the instance according to whether it wraps a private or public key.
284 30
     * @param NetworkInterface $network
285
     * @return string
286 30
     */
287 30
    public function toExtendedKey(NetworkInterface $network = null)
288 30
    {
289
        $network = $network ?: Bitcoin::getNetwork();
290
291
        $extendedSerializer = new Base58ExtendedKeySerializer(new ExtendedKeySerializer($this->ecAdapter, $network));
292
        $extended = $extendedSerializer->serialize($this);
293
        return $extended;
294
    }
295
296
    /**
297
     * Explicitly serialize as a private key. Throws an exception if
298 30
     * the key isn't private.
299
     *
300 30
     * @param NetworkInterface $network
301 6
     * @return string
302
     */
303
    public function toExtendedPrivateKey(NetworkInterface $network = null)
304 24
    {
305
        if (!$this->isPrivate()) {
306
            throw new \LogicException('Cannot create extended private key from public');
307
        }
308
309
        return $this->toExtendedKey($network);
310
    }
311
312
    /**
313 24
     * Explicitly serialize as a public key. This will always work.
314
     *
315 24
     * @param NetworkInterface $network
316 24
     * @return string
317
     */
318
    public function toExtendedPublicKey(NetworkInterface $network = null)
319
    {
320
        $clone = clone($this);
321
        return $clone->toPublic()->toExtendedKey($network);
322
    }
323
}
324