Completed
Push — master ( 576645...31172b )
by thomas
232:49 queued 163:04
created

HierarchicalKey::getHmacSeed()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 16
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

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