Completed
Pull Request — master (#662)
by thomas
38:24
created

HierarchicalKey::getScriptAndSignData()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

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