|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Blocktrail\SDK\Bitcoin; |
|
4
|
|
|
|
|
5
|
|
|
/** |
|
6
|
|
|
* Class BIP32Path |
|
7
|
|
|
* |
|
8
|
|
|
* BIP32 path, does not mutate itself but returns new instance everytime |
|
9
|
|
|
* |
|
10
|
|
|
* @package Blocktrail\SDK |
|
11
|
|
|
*/ |
|
12
|
|
|
class BIP32Path implements \ArrayAccess { |
|
13
|
|
|
protected $path; |
|
14
|
|
|
|
|
15
|
|
|
public function __construct($path) { |
|
16
|
|
|
$this->path = is_array($path) ? $path : explode("/", $path); |
|
17
|
|
|
|
|
18
|
|
|
if (strtolower($this->path[0]) != "m") { |
|
19
|
|
|
throw new \InvalidArgumentException("BIP32Path can only be used for absolute paths"); |
|
20
|
|
|
} |
|
21
|
|
|
} |
|
22
|
|
|
|
|
23
|
|
|
/** |
|
24
|
|
|
* @return int |
|
25
|
|
|
*/ |
|
26
|
|
|
public function depth() { |
|
27
|
|
|
return count($this->path); |
|
28
|
|
|
} |
|
29
|
|
|
|
|
30
|
|
|
public function insert($insert, $offset) { |
|
31
|
|
|
$path = $this->path; |
|
32
|
|
|
|
|
33
|
|
|
array_splice($path, $offset+1, 0, [$insert]); |
|
34
|
|
|
|
|
35
|
|
|
return new static($path); |
|
36
|
|
|
} |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* increase the last level of the path by 1 and return the new path |
|
40
|
|
|
* |
|
41
|
|
|
* @return BIP32Path |
|
42
|
|
|
*/ |
|
43
|
|
|
public function next() { |
|
44
|
|
|
$path = $this->path; |
|
45
|
|
|
|
|
46
|
|
|
$last = array_pop($path); |
|
47
|
|
|
|
|
48
|
|
|
if ($hardened = (strpos($last, "'") !== false)) { |
|
49
|
|
|
$last = str_replace("'", "", $last); |
|
50
|
|
|
} |
|
51
|
|
|
|
|
52
|
|
|
$last = (int)$last; |
|
53
|
|
|
$last += 1; |
|
54
|
|
|
|
|
55
|
|
|
if ($hardened) { |
|
56
|
|
|
$last .= "'"; |
|
57
|
|
|
} |
|
58
|
|
|
|
|
59
|
|
|
$path[] = $last; |
|
60
|
|
|
|
|
61
|
|
|
return new static($path); |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* pop off one level of the path and return the new path |
|
66
|
|
|
* |
|
67
|
|
|
* @return BIP32Path |
|
68
|
|
|
*/ |
|
69
|
|
|
public function parent() { |
|
70
|
|
|
$path = $this->path; |
|
71
|
|
|
|
|
72
|
|
|
array_pop($path); |
|
73
|
|
|
|
|
74
|
|
|
if (empty($path)) { |
|
75
|
|
|
throw new \RuntimeException("Can't get parent of root path"); |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
return new static($path); |
|
79
|
|
|
} |
|
80
|
|
|
|
|
81
|
|
|
/** |
|
82
|
|
|
* get child $child of the current path and return the new path |
|
83
|
|
|
* |
|
84
|
|
|
* @param $child |
|
85
|
|
|
* @return BIP32Path |
|
86
|
|
|
*/ |
|
87
|
|
|
public function child($child) { |
|
88
|
|
|
$path = $this->path; |
|
89
|
|
|
|
|
90
|
|
|
$path[] = $child; |
|
91
|
|
|
|
|
92
|
|
|
return new static($path); |
|
93
|
|
|
} |
|
94
|
|
|
|
|
95
|
|
|
/** |
|
96
|
|
|
* pop off one level of the path and add $last and return the new path |
|
97
|
|
|
* |
|
98
|
|
|
* @param $last |
|
99
|
|
|
* @return BIP32Path |
|
100
|
|
|
*/ |
|
101
|
|
|
public function last($last) { |
|
102
|
|
|
$path = $this->path; |
|
103
|
|
|
|
|
104
|
|
|
array_pop($path); |
|
105
|
|
|
$path[] = $last; |
|
106
|
|
|
|
|
107
|
|
|
return new static($path); |
|
108
|
|
|
} |
|
109
|
|
|
|
|
110
|
|
|
/** |
|
111
|
|
|
* harden the last level of the path and return the new path |
|
112
|
|
|
* |
|
113
|
|
|
* @return BIP32Path |
|
114
|
|
|
*/ |
|
115
|
|
View Code Duplication |
public function hardened() { |
|
|
|
|
|
|
116
|
|
|
$path = $this->path; |
|
117
|
|
|
|
|
118
|
|
|
$last = array_pop($path); |
|
119
|
|
|
|
|
120
|
|
|
if (strpos($last, "'") !== false) { |
|
121
|
|
|
return $this; |
|
122
|
|
|
} |
|
123
|
|
|
|
|
124
|
|
|
$last .= "'"; |
|
125
|
|
|
|
|
126
|
|
|
$path[] = $last; |
|
127
|
|
|
|
|
128
|
|
|
return new static($path); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
/** |
|
132
|
|
|
* unharden the last level of the path and return the new path |
|
133
|
|
|
* |
|
134
|
|
|
* @return BIP32Path |
|
135
|
|
|
*/ |
|
136
|
|
View Code Duplication |
public function unhardenedLast() { |
|
|
|
|
|
|
137
|
|
|
$path = $this->path; |
|
138
|
|
|
|
|
139
|
|
|
$last = array_pop($path); |
|
140
|
|
|
|
|
141
|
|
|
$last = str_replace("'", "", $last); |
|
142
|
|
|
|
|
143
|
|
|
$path[] = $last; |
|
144
|
|
|
|
|
145
|
|
|
return new static($path); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* unharden all levels of the path and return the new path |
|
150
|
|
|
* |
|
151
|
|
|
* @return BIP32Path |
|
152
|
|
|
*/ |
|
153
|
|
|
public function unhardenedPath() { |
|
154
|
|
|
$path = $this->path; |
|
155
|
|
|
|
|
156
|
|
|
foreach ($path as $i => $level) { |
|
157
|
|
|
$path[$i] = str_replace("'", "", $level); |
|
158
|
|
|
} |
|
159
|
|
|
|
|
160
|
|
|
return new static($path); |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
/** |
|
164
|
|
|
* change the path to be for the public key (starting with M/) and return the new path |
|
165
|
|
|
* |
|
166
|
|
|
* @return BIP32Path |
|
167
|
|
|
*/ |
|
168
|
|
View Code Duplication |
public function publicPath() { |
|
|
|
|
|
|
169
|
|
|
$path = $this->path; |
|
170
|
|
|
|
|
171
|
|
|
if ($path[0] === "M") { |
|
172
|
|
|
return new static($path); |
|
173
|
|
|
} else { |
|
174
|
|
|
$path[0] = "M"; |
|
175
|
|
|
|
|
176
|
|
|
return new static($path); |
|
177
|
|
|
} |
|
178
|
|
|
} |
|
179
|
|
|
|
|
180
|
|
|
/** |
|
181
|
|
|
* change the path to be for the private key (starting with m/) and return the new path |
|
182
|
|
|
* |
|
183
|
|
|
* @return BIP32Path |
|
184
|
|
|
*/ |
|
185
|
|
View Code Duplication |
public function privatePath() { |
|
|
|
|
|
|
186
|
|
|
$path = $this->path; |
|
187
|
|
|
|
|
188
|
|
|
if ($path[0] === "m") { |
|
189
|
|
|
return new static($path); |
|
190
|
|
|
} else { |
|
191
|
|
|
$path[0] = "m"; |
|
192
|
|
|
|
|
193
|
|
|
return new static($path); |
|
194
|
|
|
} |
|
195
|
|
|
} |
|
196
|
|
|
|
|
197
|
|
|
/** |
|
198
|
|
|
* get the string representation of the path |
|
199
|
|
|
* |
|
200
|
|
|
* @return string |
|
201
|
|
|
*/ |
|
202
|
|
|
public function getPath() { |
|
203
|
|
|
return implode("/", $this->path); |
|
204
|
|
|
} |
|
205
|
|
|
|
|
206
|
|
|
/** |
|
207
|
|
|
* get the last part of the path |
|
208
|
|
|
* |
|
209
|
|
|
* @return string |
|
210
|
|
|
*/ |
|
211
|
|
|
public function getLast() { |
|
212
|
|
|
return $this->path[count($this->path)-1]; |
|
213
|
|
|
} |
|
214
|
|
|
|
|
215
|
|
|
/** |
|
216
|
|
|
* check if the last level of the path is hardened |
|
217
|
|
|
* |
|
218
|
|
|
* @return bool |
|
219
|
|
|
*/ |
|
220
|
|
|
public function isHardened() { |
|
221
|
|
|
$path = $this->path; |
|
222
|
|
|
|
|
223
|
|
|
$last = array_pop($path); |
|
224
|
|
|
|
|
225
|
|
|
return strpos($last, "'") !== false; |
|
226
|
|
|
} |
|
227
|
|
|
|
|
228
|
|
|
/** |
|
229
|
|
|
* check if the last level of the path is hardened |
|
230
|
|
|
* |
|
231
|
|
|
* @return bool |
|
232
|
|
|
*/ |
|
233
|
|
|
public function isPublicPath() { |
|
234
|
|
|
$path = $this->path; |
|
235
|
|
|
|
|
236
|
|
|
return $path[0] == "M"; |
|
237
|
|
|
} |
|
238
|
|
|
|
|
239
|
|
|
/** |
|
240
|
|
|
* check if this path is parent path of the provided path |
|
241
|
|
|
* |
|
242
|
|
|
* @param string|BIP32Path $path |
|
243
|
|
|
* @return bool |
|
244
|
|
|
*/ |
|
245
|
|
|
public function isParentOf($path) { |
|
246
|
|
|
$path = BIP32Path::path($path); |
|
247
|
|
|
|
|
248
|
|
|
return strlen((string)$path) > strlen((string)$this) && strpos((string)$path, (string)$this) === 0; |
|
249
|
|
|
} |
|
250
|
|
|
|
|
251
|
|
|
/** |
|
252
|
|
|
* static method to initialize class |
|
253
|
|
|
* |
|
254
|
|
|
* @param $path |
|
255
|
|
|
* @return BIP32Path |
|
256
|
|
|
*/ |
|
257
|
|
|
public static function path($path) { |
|
258
|
|
|
if ($path instanceof static) { |
|
259
|
|
|
return $path; |
|
260
|
|
|
} |
|
261
|
|
|
|
|
262
|
|
|
return new static($path); |
|
263
|
|
|
} |
|
264
|
|
|
|
|
265
|
|
|
public function getKeyIndex() { |
|
266
|
|
|
return str_replace("'", "", $this->path[1]); |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
/** |
|
270
|
|
|
* count the levels in the path (including master) |
|
271
|
|
|
* |
|
272
|
|
|
* @return int |
|
273
|
|
|
*/ |
|
274
|
|
|
public function count() { |
|
275
|
|
|
return count($this->path); |
|
276
|
|
|
} |
|
277
|
|
|
|
|
278
|
|
|
|
|
279
|
|
|
public function offsetExists($offset) { |
|
280
|
|
|
return isset($this->path[$offset]); |
|
281
|
|
|
} |
|
282
|
|
|
|
|
283
|
|
|
public function offsetGet($offset) { |
|
284
|
|
|
return isset($this->path[$offset]) ? $this->path[$offset] : null; |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
public function offsetSet($offset, $value) { |
|
288
|
|
|
throw new \Exception("Not implemented"); |
|
289
|
|
|
} |
|
290
|
|
|
|
|
291
|
|
|
public function offsetUnset($offset) { |
|
292
|
|
|
throw new \Exception("Not implemented"); |
|
293
|
|
|
} |
|
294
|
|
|
|
|
295
|
|
|
/** |
|
296
|
|
|
* @return string |
|
297
|
|
|
*/ |
|
298
|
|
|
public function __toString() { |
|
299
|
|
|
return $this->getPath(); |
|
300
|
|
|
} |
|
301
|
|
|
} |
|
302
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.