1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace BitWasp\Bitcoin\Script\Parser; |
||
6 | |||
7 | use BitWasp\Bitcoin\Math\Math; |
||
8 | use BitWasp\Bitcoin\Script\Opcodes; |
||
9 | use BitWasp\Bitcoin\Script\Script; |
||
10 | use BitWasp\Bitcoin\Script\ScriptInterface; |
||
11 | use BitWasp\Buffertools\Buffer; |
||
12 | use BitWasp\Buffertools\BufferInterface; |
||
13 | |||
14 | class Parser implements \Iterator |
||
15 | { |
||
16 | /** |
||
17 | * @var Math |
||
18 | */ |
||
19 | private $math; |
||
20 | |||
21 | /** |
||
22 | * @var BufferInterface |
||
23 | */ |
||
24 | private $empty; |
||
25 | |||
26 | /** |
||
27 | * @var ScriptInterface |
||
28 | */ |
||
29 | private $script; |
||
30 | |||
31 | /** |
||
32 | * @var int |
||
33 | */ |
||
34 | private $count = 0; |
||
35 | |||
36 | /** |
||
37 | * @var int |
||
38 | */ |
||
39 | private $position = 0; |
||
40 | |||
41 | /** |
||
42 | * @var int |
||
43 | */ |
||
44 | private $end = 0; |
||
45 | |||
46 | /** |
||
47 | * @var int |
||
48 | */ |
||
49 | private $execPtr = 0; |
||
50 | |||
51 | /** |
||
52 | * @var string |
||
53 | */ |
||
54 | private $data = ''; |
||
55 | |||
56 | /** |
||
57 | * @var Operation[] |
||
58 | */ |
||
59 | private $array = array(); |
||
60 | |||
61 | /** |
||
62 | * ScriptParser constructor. |
||
63 | * @param Math $math |
||
64 | * @param ScriptInterface $script |
||
65 | */ |
||
66 | 4960 | public function __construct(Math $math, ScriptInterface $script) |
|
67 | { |
||
68 | 4960 | $this->math = $math; |
|
69 | 4960 | $buffer = $script->getBuffer(); |
|
70 | 4960 | $this->data = $buffer->getBinary(); |
|
71 | 4960 | $this->end = $buffer->getSize(); |
|
72 | 4960 | $this->script = $script; |
|
73 | 4960 | $this->empty = new Buffer('', 0); |
|
74 | 4960 | } |
|
75 | |||
76 | /** |
||
77 | * @return int |
||
78 | */ |
||
79 | 4 | public function getPosition(): int |
|
80 | { |
||
81 | 4 | return $this->position; |
|
82 | } |
||
83 | |||
84 | /** |
||
85 | * @param string $packFormat |
||
86 | * @param integer $strSize |
||
87 | * @return array|bool |
||
88 | */ |
||
89 | 133 | private function unpackSize(string $packFormat, int $strSize) |
|
90 | { |
||
91 | 133 | if ($this->end - $this->position < $strSize) { |
|
92 | return false; |
||
93 | } |
||
94 | |||
95 | 133 | $size = unpack($packFormat, substr($this->data, $this->position, $strSize)); |
|
96 | 133 | $size = $size[1]; |
|
97 | 133 | $this->position += $strSize; |
|
98 | |||
99 | 133 | return $size; |
|
100 | } |
||
101 | |||
102 | /** |
||
103 | * @param int $ptr |
||
104 | * @return Operation |
||
105 | */ |
||
106 | 4943 | private function doNext(int $ptr) |
|
107 | { |
||
108 | 4943 | if ($this->position >= $this->end) { |
|
109 | throw new \RuntimeException('Position exceeds end of script!'); |
||
110 | } |
||
111 | |||
112 | 4943 | $opCode = ord($this->data[$this->position++]); |
|
113 | 4943 | $pushData = $this->empty; |
|
114 | 4943 | $dataSize = 0; |
|
115 | |||
116 | 4943 | if ($opCode <= Opcodes::OP_PUSHDATA4) { |
|
117 | 3725 | if ($opCode < Opcodes::OP_PUSHDATA1) { |
|
118 | 3657 | $dataSize = $opCode; |
|
119 | 133 | } else if ($opCode === Opcodes::OP_PUSHDATA1) { |
|
120 | 74 | $dataSize = $this->unpackSize('C', 1); |
|
121 | 67 | } else if ($opCode === Opcodes::OP_PUSHDATA2) { |
|
122 | 50 | $dataSize = $this->unpackSize('v', 2); |
|
123 | } else { |
||
124 | 17 | $dataSize = $this->unpackSize('V', 4); |
|
125 | } |
||
126 | |||
127 | 3725 | $delta = ($this->end - $this->position); |
|
128 | 3725 | if ($dataSize === false || $delta < 0 || $delta < $dataSize) { |
|
129 | 15 | throw new \RuntimeException('Failed to unpack data from Script'); |
|
130 | } |
||
131 | |||
132 | 3713 | if ($dataSize > 0) { |
|
133 | 2737 | $pushData = new Buffer(substr($this->data, $this->position, $dataSize), $dataSize); |
|
134 | } |
||
135 | |||
136 | 3713 | $this->position += $dataSize; |
|
137 | } |
||
138 | |||
139 | 4931 | $this->array[$ptr] = new Operation($opCode, $pushData, $dataSize); |
|
140 | 4931 | $this->count++; |
|
141 | |||
142 | 4931 | return $this->array[$ptr]; |
|
143 | } |
||
144 | |||
145 | /** |
||
146 | * @param int $begin |
||
147 | * @param null|int $length |
||
148 | * @return Script |
||
149 | */ |
||
150 | 252 | public function slice(int $begin, int $length = null) |
|
151 | { |
||
152 | 252 | if ($begin < 0) { |
|
153 | throw new \RuntimeException("Invalid start of script - cannot be negative or "); |
||
154 | } |
||
155 | |||
156 | 252 | $maxLength = $this->end - $begin; |
|
157 | |||
158 | 252 | if (null === $length) { |
|
159 | $length = $maxLength; |
||
160 | } else { |
||
161 | 252 | if ($length > $maxLength) { |
|
162 | throw new \RuntimeException("Cannot slice this much from script"); |
||
163 | } |
||
164 | } |
||
165 | |||
166 | 252 | return new Script(new Buffer(substr($this->data, $begin, $length))); |
|
167 | } |
||
168 | |||
169 | /** |
||
170 | * |
||
171 | */ |
||
172 | 4953 | public function rewind() |
|
173 | { |
||
174 | 4953 | $this->execPtr = 0; |
|
175 | 4953 | $this->position = 0; |
|
176 | 4953 | } |
|
177 | |||
178 | /** |
||
179 | * @return Operation |
||
180 | */ |
||
181 | 4943 | public function current() |
|
182 | { |
||
183 | 4943 | if (isset($this->array[$this->execPtr])) { |
|
184 | 252 | $exec = $this->array[$this->execPtr]; |
|
185 | } else { |
||
186 | 4943 | $exec = $this->doNext($this->execPtr); |
|
187 | } |
||
188 | |||
189 | 4931 | return $exec; |
|
190 | } |
||
191 | |||
192 | /** |
||
193 | * @return int |
||
194 | */ |
||
195 | 142 | public function key() |
|
196 | { |
||
197 | 142 | return $this->execPtr; |
|
198 | } |
||
199 | |||
200 | /** |
||
201 | * @return Operation|null |
||
202 | */ |
||
203 | 4809 | public function next() |
|
204 | { |
||
205 | 4809 | $ptr = $this->execPtr; |
|
206 | 4809 | if (isset($this->array[$ptr])) { |
|
207 | 4803 | $this->execPtr++; |
|
208 | 4803 | return $this->array[$ptr]; |
|
0 ignored issues
–
show
|
|||
209 | } |
||
210 | |||
211 | 6 | return null; |
|
212 | } |
||
213 | |||
214 | /** |
||
215 | * @return bool |
||
216 | */ |
||
217 | 4953 | public function valid() |
|
218 | { |
||
219 | 4953 | return isset($this->array[$this->execPtr]) || $this->position < $this->end; |
|
220 | } |
||
221 | |||
222 | /** |
||
223 | * @return Operation[] |
||
224 | */ |
||
225 | 2481 | public function decode(): array |
|
226 | { |
||
227 | 2481 | $result = []; |
|
228 | 2481 | foreach ($this as $operation) { |
|
229 | 2465 | $result[] = $operation; |
|
230 | } |
||
231 | |||
232 | 2481 | return $result; |
|
233 | } |
||
234 | |||
235 | /** |
||
236 | * @return string |
||
237 | */ |
||
238 | 3 | public function getHumanReadable(): string |
|
239 | { |
||
240 | 3 | return implode(' ', array_map( |
|
241 | 3 | function (Operation $operation) { |
|
242 | 2 | $op = $operation->getOp(); |
|
243 | 2 | if ($op === Opcodes::OP_0 || $op === Opcodes::OP_1NEGATE || $op >= Opcodes::OP_1 && $op <= Opcodes::OP_16) { |
|
244 | return $this->script->getOpcodes()->getOp($op); |
||
245 | 2 | } else if ($operation->isPush()) { |
|
246 | 2 | return $operation->getData()->getHex(); |
|
247 | } else { |
||
248 | 1 | return $this->script->getOpcodes()->getOp($operation->getOp()); |
|
249 | } |
||
250 | 3 | }, |
|
251 | $this->decode() |
||
252 | )); |
||
253 | } |
||
254 | } |
||
255 |
In the issue above, the returned value is violating the contract defined by the mentioned interface.
Let's take a look at an example: