Completed
Pull Request — 0.0.35 (#660)
by thomas
29:47 queued 20:44
created

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