Completed
Pull Request — 0.0.35 (#659)
by thomas
25:58
created

HierarchicalKey::getChainCode()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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\Adapter\EcAdapterInterface;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\KeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
10
use BitWasp\Bitcoin\Crypto\Hash;
11
use BitWasp\Bitcoin\Network\NetworkInterface;
12
use BitWasp\Bitcoin\Serializer\Key\HierarchicalKey\Base58ExtendedKeySerializer;
13
use BitWasp\Bitcoin\Serializer\Key\HierarchicalKey\ExtendedKeySerializer;
14
use BitWasp\Bitcoin\Util\IntRange;
15
use BitWasp\Buffertools\Buffer;
16
use BitWasp\Buffertools\BufferInterface;
17
use BitWasp\Buffertools\Buffertools;
18
use BitWasp\Buffertools\Parser;
19
20
class HierarchicalKey
21
{
22
    /**
23
     * @var EcAdapterInterface
24
     */
25
    protected $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
     */
61 56
    public function __construct(EcAdapterInterface $ecAdapter, $depth, $parentFingerprint, $sequence, BufferInterface $chainCode, KeyInterface $key)
62
    {
63 56
        if ($depth < 0 || $depth > IntRange::U8_MAX) {
64
            throw new \InvalidArgumentException('Invalid depth for BIP32 key, must be in range [0 - 255] inclusive');
65
        }
66
67 56
        if ($parentFingerprint < 0 || $parentFingerprint > IntRange::U32_MAX) {
68
            throw new \InvalidArgumentException('Invalid fingerprint for BIP32 key, must be in range [0 - (2^31)-1] inclusive');
69
        }
70
71 56
        if ($sequence < 0 || $sequence > IntRange::U32_MAX) {
72
            throw new \InvalidArgumentException('Invalid sequence for BIP32 key, must be in range [0 - (2^31)-1] inclusive');
73
        }
74
75 56
        if ($chainCode->getSize() !== 32) {
76
            throw new \RuntimeException('Chaincode should be 32 bytes');
77
        }
78
79 56
        if (!$key->isCompressed()) {
80 1
            throw new \InvalidArgumentException('A HierarchicalKey must always be compressed');
81
        }
82
83 55
        $this->ecAdapter = $ecAdapter;
84 55
        $this->depth = $depth;
85 55
        $this->sequence = $sequence;
86 55
        $this->parentFingerprint = $parentFingerprint;
87 55
        $this->chainCode = $chainCode;
88 55
        $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...
89 55
    }
90
91
    /**
92
     * Return the depth of this key. This is limited to 256 sequential derivations.
93
     *
94
     * @return int
95
     */
96 21
    public function getDepth()
97
    {
98 21
        return $this->depth;
99
    }
100
101
    /**
102
     * Get the sequence number for this address. Hardened keys are
103
     * created with sequence > 0x80000000. a sequence number lower
104
     * than this can be derived with the public key.
105
     *
106
     * @return int
107
     */
108 16
    public function getSequence()
109
    {
110 16
        return $this->sequence;
111
    }
112
113
    /**
114
     * Get the fingerprint of the parent key. For master keys, this is 00000000.
115
     *
116
     * @return string
117
     */
118 17
    public function getFingerprint()
119
    {
120 17
        if ($this->getDepth() === 0) {
121 11
            return 0;
122
        }
123
124 17
        return $this->parentFingerprint;
125
    }
126
127
    /**
128
     * Return the fingerprint to be used for child keys.
129
     * @return int
130
     */
131 18
    public function getChildFingerprint()
132
    {
133 18
        return $this->getPublicKey()->getPubKeyHash()->slice(0, 4)->getInt();
134
    }
135
136
    /**
137
     * Return the chain code - a deterministic 'salt' for HMAC-SHA512
138
     * in child derivations
139
     *
140
     * @return BufferInterface
141
     */
142 15
    public function getChainCode()
143
    {
144 15
        return $this->chainCode;
145
    }
146
147
    /**
148
     * @return PrivateKeyInterface
149
     */
150 27
    public function getPrivateKey()
151
    {
152 27
        if ($this->key->isPrivate()) {
153 25
            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 153 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...
154
        }
155
156 2
        throw new \RuntimeException('Unable to get private key, not known');
157
    }
158
159
    /**
160
     * Get the public key the private key or public key.
161
     *
162
     * @return PublicKeyInterface
163
     */
164 29
    public function getPublicKey()
165
    {
166 29
        if ($this->isPrivate()) {
167 21
            return $this->getPrivateKey()->getPublicKey();
168
        } else {
169 18
            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 169 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...
170
        }
171
    }
172
173
    /**
174
     * @return HierarchicalKey
175
     */
176 12
    public function withoutPrivateKey()
177
    {
178 12
        $clone = clone $this;
179 12
        $clone->key = $clone->getPublicKey();
180 12
        return $clone;
181
    }
182
183
    /**
184
     * Return whether this is a private key
185
     *
186
     * @return bool
187
     */
188 33
    public function isPrivate()
189
    {
190 33
        return $this->key->isPrivate();
191
    }
192
193
    /**
194
     * Return whether the key is hardened
195
     *
196
     * @return bool
197
     */
198 2
    public function isHardened()
199
    {
200 2
        return ($this->sequence >> 31) === 1;
201
    }
202
203
    /**
204
     * Create a buffer containing data to be hashed hashed to yield the child offset
205
     *
206
     * @param int $sequence
207
     * @return BufferInterface
208
     * @throws \Exception
209
     */
210 24
    public function getHmacSeed($sequence)
211
    {
212 24
        if ($sequence < 0 || $sequence > IntRange::U32_MAX) {
213 6
            throw new \InvalidArgumentException("Sequence is outside valid range, must be >= 0 && <= (2^31)-1");
214
        }
215
216 18
        if (($sequence >> 31) === 1) {
217 15
            if ($this->isPrivate() === false) {
218 2
                throw new \Exception("Can't derive a hardened key without the private key");
219
            }
220
221 13
            $buffer = Buffertools::concat(new Buffer("\x00"), $this->getPrivateKey()->getBuffer());
222
        } else {
223 16
            $buffer = $this->getPublicKey()->getBuffer();
224
        }
225
226 16
        return (new Parser($buffer))
227 16
            ->writeBytes(4, Buffer::int($sequence, 4))
228 16
            ->getBuffer();
229
    }
230
231
    /**
232
     * @param $nextDepth
233
     * @param $sequence
234
     * @param BufferInterface $chainCode
235
     * @param KeyInterface $key
236
     * @return HierarchicalKey
237
     * @throws \Exception
238
     */
239 16
    protected function childKey($nextDepth, $sequence, BufferInterface $chainCode, KeyInterface $key)
240
    {
241 16
        return new HierarchicalKey(
242 16
            $this->ecAdapter,
243 16
            $nextDepth,
244 16
            $this->getChildFingerprint(),
245 16
            $sequence,
246 16
            $chainCode,
247 16
            $key
248
        );
249
    }
250
251
    /**
252
     * Derive a child key
253
     *
254
     * @param int $sequence
255
     * @return HierarchicalKey
256
     * @throws \Exception
257
     */
258 21
    public function deriveChild($sequence)
259
    {
260 21
        $nextDepth = $this->depth + 1;
261 21
        if ($nextDepth > 255) {
262
            throw new \InvalidArgumentException('Invalid depth for BIP32 key, cannot exceed 255');
263
        }
264
265 21
        $hash = Hash::hmac('sha512', $this->getHmacSeed($sequence), $this->chainCode);
266 16
        $offset = $hash->slice(0, 32);
267 16
        $chain = $hash->slice(32);
268
269 16
        if (false === $this->ecAdapter->validatePrivateKey($offset)) {
270 1
            return $this->deriveChild($sequence + 1);
271
        }
272
273 16
        $key = $this->isPrivate() ? $this->getPrivateKey() : $this->getPublicKey();
274 16
        $key = $key->tweakAdd($offset->getGmp());
275
276 16
        return $this->childKey($nextDepth, $sequence, $chain, $key);
277
    }
278
279
    /**
280
     * @param array|\stdClass|\Traversable $list
281
     * @return HierarchicalKey
282
     */
283 11
    public function deriveFromList($list)
284
    {
285 11
        if (!is_array($list) && !$list instanceof \Traversable && !$list instanceof \stdClass) {
286
            throw new \InvalidArgumentException('List must be an array or \Traversable');
287
        }
288
289 11
        $key = $this;
290 11
        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...
291 11
            $key = $key->deriveChild($sequence);
292
        }
293
294 11
        return $key;
295
    }
296
297
    /**
298
     * Decodes a BIP32 path into actual 32bit sequence numbers and derives the child key
299
     *
300
     * @param string $path
301
     * @return HierarchicalKey
302
     * @throws \Exception
303
     */
304 11
    public function derivePath($path)
305
    {
306 11
        $sequences = new HierarchicalKeySequence();
307 11
        return $this->deriveFromList($sequences->decodePath($path));
308
    }
309
310
    /**
311
     * Serializes the instance according to whether it wraps a private or public key.
312
     * @param NetworkInterface $network
313
     * @return string
314
     */
315 12
    public function toExtendedKey(NetworkInterface $network = null)
316
    {
317 12
        $network = $network ?: Bitcoin::getNetwork();
318
319 12
        $extendedSerializer = new Base58ExtendedKeySerializer(new ExtendedKeySerializer($this->ecAdapter));
320 12
        $extended = $extendedSerializer->serialize($network, $this);
321 12
        return $extended;
322
    }
323
324
    /**
325
     * Explicitly serialize as a private key. Throws an exception if
326
     * the key isn't private.
327
     *
328
     * @param NetworkInterface $network
329
     * @return string
330
     */
331 12
    public function toExtendedPrivateKey(NetworkInterface $network = null)
332
    {
333 12
        if (!$this->isPrivate()) {
334 2
            throw new \LogicException('Cannot create extended private key from public');
335
        }
336
337 10
        return $this->toExtendedKey($network);
338
    }
339
340
    /**
341
     * Explicitly serialize as a public key. This will always work.
342
     *
343
     * @param NetworkInterface $network
344
     * @return string
345
     */
346 9
    public function toExtendedPublicKey(NetworkInterface $network = null)
347
    {
348 9
        return $this->withoutPrivateKey()->toExtendedKey($network);
349
    }
350
}
351