Completed
Push — master ( 460d12...eabe8c )
by thomas
24:52
created

HierarchicalKeySequence::decodeAbsolute()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 0
Metric Value
cc 4
nc 3
nop 1
dl 0
loc 16
ccs 0
cts 8
cp 0
crap 20
rs 9.7333
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Key\Deterministic;
6
7
class HierarchicalKeySequence
8
{
9
    private static $filterBip32Index = [
10
        'min_range' => 0,
11
        'max_range' => (1 << 31) - 1,
12
    ];
13
14
    /**
15
     * decodeAbsolute accepts an absolute BIP32 path, one beginning
16
     * with m or M. It returns an array, containing two elements.
17
     * The first is whether the prefix was public or private.
18
     * The second is the array of indices contained in the path.
19
     *
20
     * @param string $path
21
     * @return array - <isPrivate bool, path <int[]>>
22
     */
23
    public function decodeAbsolute(string $path)
24
    {
25
        $parts = explode("/", $path);
26
        if (count($parts) < 1) {
27
            throw new \InvalidArgumentException("Invalid BIP32 path - must have at least one component");
28
        }
29
30
        if (!($parts[0] === "m" || $parts[0] === "M")) {
31
            throw new \InvalidArgumentException("Invalid start of absolute BIP32 path - should be m or M");
32
        }
33
34
        return [
35
            $parts[0] === "m",
36
            $this->decodeDerivation(...array_slice($parts, 1)),
37
        ];
38
    }
39
40
    /**
41
     * decodeRelative accepts a relative BIP32 path, that is,
42
     * one without the prefix of m or M. These are usually provided
43
     * when requesting some child derivation of a key.
44
     *
45
     * @param string $path
46
     * @return int[]
47
     */
48 44
    public function decodeRelative(string $path): array
49
    {
50 44
        $parts = explode("/", $path);
51 44
        if (count($parts) < 1) {
52
            throw new \InvalidArgumentException("Invalid BIP32 path - must have at least one component");
53
        }
54
55 44
        if ($parts[0] === "m" || $parts[0] === "M") {
56
            throw new \InvalidArgumentException("Only relative paths accepted");
57
        }
58
59 44
        return $this->decodeDerivation(...$parts);
60
    }
61
62
    /**
63
     * Inner routine for decoding the numeric section of a path
64
     * @param string ...$parts
65
     * @return int[]
66
     */
67 44
    private function decodeDerivation(string... $parts): array
68
    {
69 44
        $indices = [];
70 44
        foreach ($parts as $i => $part) {
71 44
            if ($part === "") {
72 1
                throw new \InvalidArgumentException("Invalid BIP32 path - Empty path section");
73
            }
74
75
            // test the last character for hardened
76 43
            $last = substr($part, -1);
77 43
            $hardened = $last == "h" || $last == "'";
78 43
            if ($hardened) {
79 39
                if (strlen($part) === 1) {
80
                    throw new \InvalidArgumentException("Invalid BIP32 path - section contains only hardened flag");
81
                }
82 39
                $part = substr($part, 0, -1);
83
            }
84
85
            // any other hardened chars are invalid
86 43
            if (false !== strpos($part, "h") || false !== strpos($part, "'")) {
87
                throw new \InvalidArgumentException("Invalid BIP32 path - section contains extra hardened characters");
88
            }
89
90
            // validate part is a valid integer
91 43
            if (false === filter_var($part, FILTER_VALIDATE_INT, self::$filterBip32Index)) {
92
                throw new \InvalidArgumentException("Index is invalid or outside valid range: $part");
93
            }
94
95
            // make index from int $part + $hardened
96 43
            $index = (int) $part;
97 43
            if ($hardened) {
98 39
                $index |= 1 << 31;
99
            }
100 43
            $indices[] = $index;
101
        }
102 43
        return $indices;
103
    }
104
}
105