Completed
Push — master ( e0c844...6fbee2 )
by thomas
14:53
created

HierarchicalKey   A

Complexity

Total Complexity 34

Size/Duplication

Total Lines 300
Duplicated Lines 0 %

Coupling/Cohesion

Components 2
Dependencies 13

Test Coverage

Coverage 97.78%

Importance

Changes 7
Bugs 1 Features 0
Metric Value
c 7
b 1
f 0
dl 0
loc 300
ccs 88
cts 90
cp 0.9778
rs 9.2
wmc 34
lcom 2
cbo 13

18 Methods

Rating   Name   Duplication   Size   Complexity  
B deriveFromList() 0 13 5
A derivePath() 0 5 1
A toExtendedKey() 0 8 2
A toExtendedPrivateKey() 0 8 2
A toExtendedPublicKey() 0 5 1
A getDepth() 0 4 1
A getSequence() 0 4 1
A getFingerprint() 0 8 2
A getChildFingerprint() 0 4 1
A getChainCode() 0 4 1
A getPrivateKey() 0 8 2
A getPublicKey() 0 8 2
A toPublic() 0 8 2
A isPrivate() 0 4 1
A isHardened() 0 4 1
A getHmacSeed() 0 16 3
A deriveChild() 0 22 3
A __construct() 0 17 3
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 75
    public function __construct(EcAdapterInterface $ecAdapter, $depth, $parentFingerprint, $sequence, BufferInterface $chainCode, KeyInterface $key)
61
    {
62 75
        if ($chainCode->getSize() !== 32) {
63
            throw new \RuntimeException('Chaincode should be 32 bytes');
64
        }
65
66 75
        if (!$key->isCompressed()) {
67 3
            throw new \InvalidArgumentException('A HierarchicalKey must always be compressed');
68
        }
69
70 72
        $this->ecAdapter = $ecAdapter;
71 72
        $this->depth = $depth;
72 72
        $this->sequence = $sequence;
73 72
        $this->parentFingerprint = $parentFingerprint;
74 72
        $this->chainCode = $chainCode;
75 72
        $this->key = $key;
76 72
    }
77
78
    /**
79
     * Return the depth of this key. This is limited to 256 sequential derivations.
80
     *
81
     * @return int
82
     */
83 33
    public function getDepth()
84
    {
85 33
        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 18
    public function getSequence()
96
    {
97 18
        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 18
    public function getFingerprint()
106
    {
107 18
        if ($this->getDepth() === 0) {
108 9
            return 0;
109
        }
110
111 18
        return $this->parentFingerprint;
112
    }
113
114
    /**
115
     * Return the fingerprint to be used for child keys.
116
     * @return int
117
     */
118 21
    public function getChildFingerprint()
119
    {
120 21
        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 24
    public function getChainCode()
130
    {
131 24
        return $this->chainCode;
132
    }
133
134
    /**
135
     * @return PrivateKeyInterface
136
     */
137 42
    public function getPrivateKey()
138
    {
139 42
        if ($this->key->isPrivate()) {
140 39
            return $this->key;
141
        }
142
143 3
        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 42
    public function getPublicKey()
152
    {
153 42
        if ($this->isPrivate()) {
154 30
            return $this->getPrivateKey()->getPublicKey();
155
        } else {
156 18
            return $this->key;
157
        }
158 1
    }
159
160
    /**
161
     * @return HierarchicalKey
162
     */
163 12
    public function toPublic()
164
    {
165 12
        if ($this->isPrivate()) {
166 9
            $this->key = $this->getPrivateKey()->getPublicKey();
167 3
        }
168
169 12
        return $this;
170
    }
171
172
    /**
173
     * Return whether this is a private key
174
     *
175
     * @return bool
176
     */
177 48
    public function isPrivate()
178
    {
179 48
        return $this->key->isPrivate();
180
    }
181
182
    /**
183
     * Return whether the key is hardened
184
     *
185
     * @return bool
186
     */
187 3
    public function isHardened()
188
    {
189 3
        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 21
    public function getHmacSeed($sequence)
200
    {
201 21
        if (($sequence >> 31) === 1) {
202 15
            if ($this->isPrivate() === false) {
203 3
                throw new \Exception("Can't derive a hardened key without the private key");
204
            }
205
206 12
            $buffer = Buffertools::concat(new Buffer("\x00"), $this->getPrivateKey()->getBuffer());
207 4
        } else {
208 18
            $buffer = $this->getPublicKey()->getBuffer();
209
        }
210
211 18
        return (new Parser($buffer))
212 18
            ->writeBytes(4, Buffer::int($sequence, 4))
213 18
            ->getBuffer();
214
    }
215
216
    /**
217
     * Derive a child key
218
     *
219
     * @param int $sequence
220
     * @return HierarchicalKey
221
     * @throws \Exception
222
     */
223 21
    public function deriveChild($sequence)
224
    {
225 21
        $hash = Hash::hmac('sha512', $this->getHmacSeed($sequence), $this->getChainCode());
226 18
        $offset = $hash->slice(0, 32);
227 18
        $chain = $hash->slice(32);
228
229 18
        if (false === $this->ecAdapter->validatePrivateKey($offset)) {
230 3
            return $this->deriveChild($sequence + 1);
231
        }
232
233 18
        $key = $this->isPrivate() ? $this->getPrivateKey() : $this->getPublicKey();
234 18
        $key = $key->tweakAdd($offset->getGmp());
235
236 18
        return new HierarchicalKey(
237 18
            $this->ecAdapter,
238 18
            $this->getDepth() + 1,
239 18
            $this->getChildFingerprint(),
240 6
            $sequence,
241 6
            $chain,
242
            $key
243 6
        );
244
    }
245
246
    /**
247
     * @param array|\stdClass|\Traversable $list
248
     * @return HierarchicalKey
249
     */
250 9
    public function deriveFromList($list)
251
    {
252 9
        if (!is_array($list) && !$list instanceof \Traversable && !$list instanceof \stdClass) {
253
            throw new \InvalidArgumentException('List must be an array or \Traversable');
254
        }
255
256 9
        $key = $this;
257 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...
258 9
            $key = $key->deriveChild($sequence);
259 3
        }
260
261 9
        return $key;
262
    }
263
264
    /**
265
     * Decodes a BIP32 path into actual 32bit sequence numbers and derives the child key
266
     *
267
     * @param string $path
268
     * @return HierarchicalKey
269
     * @throws \Exception
270
     */
271 9
    public function derivePath($path)
272
    {
273 9
        $sequences = new HierarchicalKeySequence();
274 9
        return $this->deriveFromList($sequences->decodePath($path));
275
    }
276
277
    /**
278
     * Serializes the instance according to whether it wraps a private or public key.
279
     * @param NetworkInterface $network
280
     * @return string
281
     */
282 15
    public function toExtendedKey(NetworkInterface $network = null)
283
    {
284 15
        $network = $network ?: Bitcoin::getNetwork();
285
286 15
        $extendedSerializer = new Base58ExtendedKeySerializer(new ExtendedKeySerializer($this->ecAdapter, $network));
287 15
        $extended = $extendedSerializer->serialize($this);
288 15
        return $extended;
289
    }
290
291
    /**
292
     * Explicitly serialize as a private key. Throws an exception if
293
     * the key isn't private.
294
     *
295
     * @param NetworkInterface $network
296
     * @return string
297
     */
298 15
    public function toExtendedPrivateKey(NetworkInterface $network = null)
299
    {
300 15
        if (!$this->isPrivate()) {
301 3
            throw new \LogicException('Cannot create extended private key from public');
302
        }
303
304 12
        return $this->toExtendedKey($network);
305
    }
306
307
    /**
308
     * Explicitly serialize as a public key. This will always work.
309
     *
310
     * @param NetworkInterface $network
311
     * @return string
312
     */
313 12
    public function toExtendedPublicKey(NetworkInterface $network = null)
314
    {
315 12
        $clone = clone($this);
316 12
        return $clone->toPublic()->toExtendedKey($network);
317
    }
318
}
319