Completed
Push — 0.0.35 ( 990123...3dba2e )
by thomas
26:14
created

HierarchicalKey::getPublicKey()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 6
c 0
b 0
f 0
nc 2
nop 0
dl 0
loc 10
ccs 5
cts 5
cp 1
crap 2
rs 9.4285
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 77
    public function __construct(EcAdapterInterface $ecAdapter, ScriptDataFactory $scriptDataFactory, $depth, $parentFingerprint, $sequence, BufferInterface $chainCode, KeyInterface $key)
75
    {
76 77
        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 77
        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 77
        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 77
        if ($chainCode->getSize() !== 32) {
89
            throw new \RuntimeException('Chaincode should be 32 bytes');
90
        }
91
92 77
        if (!$key->isCompressed()) {
93 1
            throw new \InvalidArgumentException('A HierarchicalKey must always be compressed');
94
        }
95
96 76
        $this->ecAdapter = $ecAdapter;
97 76
        $this->depth = $depth;
98 76
        $this->sequence = $sequence;
99 76
        $this->parentFingerprint = $parentFingerprint;
100 76
        $this->chainCode = $chainCode;
101 76
        $this->key = $key;
102 76
        $this->scriptDataFactory = $scriptDataFactory;
103 76
    }
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
     * @return ScriptDataFactory
203
     */
204 30
    public function getScriptDataFactory()
205
    {
206 30
        return $this->scriptDataFactory;
207
    }
208
209
    /**
210
     * @return \BitWasp\Bitcoin\Key\KeyToScript\ScriptAndSignData
211
     */
212 26
    public function getScriptAndSignData()
213
    {
214 26
        if (null === $this->scriptAndSignData) {
215 26
            $this->scriptAndSignData = $this->scriptDataFactory->convertKey($this->key);
216
        }
217
218 26
        return $this->scriptAndSignData;
219
    }
220
221
    /**
222
     * @param BaseAddressCreator $addressCreator
223
     * @return \BitWasp\Bitcoin\Address\Address
224
     */
225 20
    public function getAddress(BaseAddressCreator $addressCreator)
226
    {
227 20
        return $this->getScriptAndSignData()->getAddress($addressCreator);
228
    }
229
230
    /**
231
     * Return whether this is a private key
232
     *
233
     * @return bool
234
     */
235 52
    public function isPrivate()
236
    {
237 52
        return $this->key->isPrivate();
238
    }
239
240
    /**
241
     * Return whether the key is hardened
242
     *
243
     * @return bool
244
     */
245 2
    public function isHardened()
246
    {
247 2
        return ($this->sequence >> 31) === 1;
248
    }
249
250
    /**
251
     * Create a buffer containing data to be hashed hashed to yield the child offset
252
     *
253
     * @param int $sequence
254
     * @return BufferInterface
255
     * @throws \Exception
256
     */
257 43
    public function getHmacSeed($sequence)
258
    {
259 43
        if ($sequence < 0 || $sequence > IntRange::U32_MAX) {
260 6
            throw new \InvalidArgumentException("Sequence is outside valid range, must be >= 0 && <= (2^31)-1");
261
        }
262
263 37
        if (($sequence >> 31) === 1) {
264 34
            if ($this->isPrivate() === false) {
265 2
                throw new \Exception("Can't derive a hardened key without the private key");
266
            }
267
268 32
            $buffer = Buffertools::concat(new Buffer("\x00"), $this->getPrivateKey()->getBuffer());
269
        } else {
270 35
            $buffer = $this->getPublicKey()->getBuffer();
271
        }
272
273 35
        return (new Parser($buffer))
274 35
            ->writeBytes(4, Buffer::int($sequence, 4))
275 35
            ->getBuffer();
276
    }
277
278
    /**
279
     * Derive a child key
280
     *
281
     * @param int $sequence
282
     * @return HierarchicalKey
283
     * @throws \Exception
284
     */
285 40
    public function deriveChild($sequence)
286
    {
287 40
        $nextDepth = $this->depth + 1;
288 40
        if ($nextDepth > 255) {
289
            throw new \InvalidArgumentException('Invalid depth for BIP32 key, cannot exceed 255');
290
        }
291
292 40
        $hash = Hash::hmac('sha512', $this->getHmacSeed($sequence), $this->chainCode);
293 35
        $offset = $hash->slice(0, 32);
294 35
        $chain = $hash->slice(32);
295
296 35
        if (false === $this->ecAdapter->validatePrivateKey($offset)) {
297 1
            return $this->deriveChild($sequence + 1);
298
        }
299
300 35
        $key = $this->isPrivate() ? $this->getPrivateKey() : $this->getPublicKey();
301 35
        $key = $key->tweakAdd($offset->getGmp());
302
303 35
        return new HierarchicalKey(
304 35
            $this->ecAdapter,
305 35
            $this->scriptDataFactory,
306 35
            $nextDepth,
307 35
            $this->getChildFingerprint(),
308 35
            $sequence,
309 35
            $chain,
310 35
            $key
311
        );
312
    }
313
314
    /**
315
     * @param array|\stdClass|\Traversable $list
316
     * @return HierarchicalKey
317
     * @throws \Exception
318
     */
319 30
    public function deriveFromList($list)
320
    {
321 30
        if (!is_array($list) && !$list instanceof \Traversable && !$list instanceof \stdClass) {
322
            throw new \InvalidArgumentException('List must be an array or \Traversable');
323
        }
324
325 30
        $key = $this;
326 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...
327 30
            $key = $key->deriveChild($sequence);
328
        }
329
330 30
        return $key;
331
    }
332
333
    /**
334
     * Decodes a BIP32 path into actual 32bit sequence numbers and derives the child key
335
     *
336
     * @param string $path
337
     * @return HierarchicalKey
338
     * @throws \Exception
339
     */
340 30
    public function derivePath($path)
341
    {
342 30
        $sequences = new HierarchicalKeySequence();
343 30
        return $this->deriveFromList($sequences->decodePath($path));
344
    }
345
346
    /**
347
     * Serializes the instance according to whether it wraps a private or public key.
348
     * @param NetworkInterface $network
349
     * @return string
350
     */
351 16
    public function toExtendedKey(NetworkInterface $network = null)
352
    {
353 16
        $network = $network ?: Bitcoin::getNetwork();
354
355 16
        $extendedSerializer = new Base58ExtendedKeySerializer(new ExtendedKeySerializer($this->ecAdapter));
356 16
        $extended = $extendedSerializer->serialize($network, $this);
357 16
        return $extended;
358
    }
359
360
    /**
361
     * Explicitly serialize as a private key. Throws an exception if
362
     * the key isn't private.
363
     *
364
     * @param NetworkInterface $network
365
     * @return string
366
     */
367 16
    public function toExtendedPrivateKey(NetworkInterface $network = null)
368
    {
369 16
        if (!$this->isPrivate()) {
370 2
            throw new \LogicException('Cannot create extended private key from public');
371
        }
372
373 14
        return $this->toExtendedKey($network);
374
    }
375
376
    /**
377
     * Explicitly serialize as a public key. This will always work.
378
     *
379
     * @param NetworkInterface $network
380
     * @return string
381
     */
382 13
    public function toExtendedPublicKey(NetworkInterface $network = null)
383
    {
384 13
        return $this->withoutPrivateKey()->toExtendedKey($network);
385
    }
386
}
387