1 | <?php |
||
2 | |||
3 | declare(strict_types=1); |
||
4 | |||
5 | namespace BitWasp\Bitcoin\Script; |
||
6 | |||
7 | use BitWasp\Bitcoin\Bitcoin; |
||
8 | use BitWasp\Bitcoin\Crypto\Hash; |
||
9 | use BitWasp\Bitcoin\Script\Classifier\OutputClassifier; |
||
10 | use BitWasp\Bitcoin\Script\Interpreter\InterpreterInterface; |
||
11 | use BitWasp\Bitcoin\Script\Interpreter\Number; |
||
12 | use BitWasp\Bitcoin\Script\Parser\Parser; |
||
13 | use BitWasp\Bitcoin\Serializable; |
||
14 | use BitWasp\Buffertools\Buffer; |
||
15 | use BitWasp\Buffertools\BufferInterface; |
||
16 | |||
17 | class Script extends Serializable implements ScriptInterface |
||
18 | { |
||
19 | |||
20 | /** |
||
21 | * @var Opcodes |
||
22 | */ |
||
23 | protected $opCodes; |
||
24 | |||
25 | /** |
||
26 | * @var string |
||
27 | */ |
||
28 | protected $script; |
||
29 | |||
30 | /** |
||
31 | * @var BufferInterface|null |
||
32 | */ |
||
33 | protected $scriptHash; |
||
34 | |||
35 | /** |
||
36 | * @var BufferInterface|null |
||
37 | */ |
||
38 | protected $witnessScriptHash; |
||
39 | |||
40 | /** |
||
41 | * @param BufferInterface $script |
||
42 | * @param Opcodes|null $opCodes |
||
43 | */ |
||
44 | 5498 | public function __construct(BufferInterface $script = null, Opcodes $opCodes = null) |
|
45 | { |
||
46 | 5498 | $this->script = $script instanceof BufferInterface ? $script->getBinary() : ''; |
|
47 | 5498 | $this->opCodes = $opCodes ?: new Opcodes(); |
|
48 | 5498 | } |
|
49 | |||
50 | /** |
||
51 | * @return BufferInterface |
||
52 | */ |
||
53 | 5516 | public function getBuffer(): BufferInterface |
|
54 | { |
||
55 | 5516 | return new Buffer($this->script); |
|
56 | } |
||
57 | |||
58 | /** |
||
59 | * @return Parser |
||
60 | */ |
||
61 | 4960 | public function getScriptParser(): Parser |
|
62 | { |
||
63 | 4960 | return new Parser(Bitcoin::getMath(), $this); |
|
64 | } |
||
65 | |||
66 | /** |
||
67 | * Get all opcodes |
||
68 | * |
||
69 | * @return Opcodes |
||
70 | */ |
||
71 | 5 | public function getOpCodes(): Opcodes |
|
72 | { |
||
73 | 5 | return $this->opCodes; |
|
74 | } |
||
75 | |||
76 | /** |
||
77 | * Return a buffer containing the HASH160 of this script. |
||
78 | * |
||
79 | * @return BufferInterface |
||
80 | */ |
||
81 | 82 | public function getScriptHash(): BufferInterface |
|
82 | { |
||
83 | 82 | if (null === $this->scriptHash) { |
|
84 | 80 | $this->scriptHash = Hash::sha256ripe160($this->getBuffer()); |
|
85 | } |
||
86 | |||
87 | 82 | return $this->scriptHash; |
|
88 | } |
||
89 | |||
90 | /** |
||
91 | * Return a buffer containing the SHA256 of this script. |
||
92 | * |
||
93 | * @return BufferInterface |
||
94 | */ |
||
95 | 284 | public function getWitnessScriptHash(): BufferInterface |
|
96 | { |
||
97 | 284 | if (null === $this->witnessScriptHash) { |
|
98 | 284 | $this->witnessScriptHash = Hash::sha256($this->getBuffer()); |
|
99 | } |
||
100 | |||
101 | 284 | return $this->witnessScriptHash; |
|
102 | } |
||
103 | |||
104 | /** |
||
105 | * @param bool|true $accurate |
||
106 | * @return int |
||
107 | */ |
||
108 | 9 | public function countSigOps(bool $accurate = true): int |
|
109 | { |
||
110 | 9 | $count = 0; |
|
111 | 9 | $parser = $this->getScriptParser(); |
|
112 | |||
113 | 9 | $lastOp = 0xff; |
|
114 | try { |
||
115 | 9 | foreach ($parser as $exec) { |
|
116 | 6 | $op = $exec->getOp(); |
|
117 | |||
118 | // None of these are pushdatas, so just an opcode |
||
119 | 6 | if ($op === Opcodes::OP_CHECKSIG || $op === Opcodes::OP_CHECKSIGVERIFY) { |
|
120 | 3 | $count++; |
|
121 | 6 | } elseif ($op === Opcodes::OP_CHECKMULTISIG || $op === Opcodes::OP_CHECKMULTISIGVERIFY) { |
|
122 | 5 | if ($accurate && ($lastOp >= Opcodes::OP_1 && $lastOp <= Opcodes::OP_16)) { |
|
123 | 4 | $count += decodeOpN($lastOp); |
|
124 | } else { |
||
125 | 2 | $count += 20; |
|
126 | } |
||
127 | } |
||
128 | |||
129 | 6 | $lastOp = $op; |
|
130 | } |
||
131 | 1 | } catch (\Exception $e) { |
|
132 | /* Script parsing failures don't count, and terminate the loop */ |
||
133 | } |
||
134 | |||
135 | 9 | return $count; |
|
136 | } |
||
137 | |||
138 | /** |
||
139 | * @param WitnessProgram $program |
||
140 | * @param ScriptWitnessInterface $scriptWitness |
||
141 | * @return int |
||
142 | */ |
||
143 | 1 | private function witnessSigOps(WitnessProgram $program, ScriptWitnessInterface $scriptWitness): int |
|
144 | { |
||
145 | 1 | if ($program->getVersion() === 0) { |
|
146 | 1 | $size = $program->getProgram()->getSize(); |
|
147 | 1 | if ($size === 32 && count($scriptWitness) > 0) { |
|
148 | 1 | $script = new Script($scriptWitness->bottom()); |
|
149 | 1 | return $script->countSigOps(true); |
|
150 | } |
||
151 | |||
152 | if ($size === 20) { |
||
153 | return 1; |
||
154 | } |
||
155 | } |
||
156 | |||
157 | return 0; |
||
158 | } |
||
159 | |||
160 | /** |
||
161 | * @param ScriptInterface $scriptSig |
||
162 | * @param ScriptWitnessInterface $scriptWitness |
||
163 | * @param int $flags |
||
164 | * @return int |
||
165 | */ |
||
166 | 1 | public function countWitnessSigOps(ScriptInterface $scriptSig, ScriptWitnessInterface $scriptWitness, int $flags): int |
|
167 | { |
||
168 | 1 | if (($flags & InterpreterInterface::VERIFY_WITNESS) === 0) { |
|
169 | return 0; |
||
170 | } |
||
171 | |||
172 | 1 | $program = null; |
|
173 | 1 | if ($this->isWitness($program)) { |
|
174 | /** @var WitnessProgram $program */ |
||
175 | 1 | return $this->witnessSigOps($program, $scriptWitness); |
|
176 | } |
||
177 | |||
178 | if ((new OutputClassifier())->isPayToScriptHash($this)) { |
||
179 | $parsed = $scriptSig->getScriptParser()->decode(); |
||
180 | $count = count($parsed); |
||
181 | if ($count > 0) { |
||
182 | $subscript = new Script($parsed[$count - 1]->getData()); |
||
183 | if ($subscript->isWitness($program)) { |
||
184 | /** @var WitnessProgram $program */ |
||
185 | return $this->witnessSigOps($program, $scriptWitness); |
||
186 | } |
||
187 | } |
||
188 | } |
||
189 | |||
190 | return 0; |
||
191 | } |
||
192 | |||
193 | /** |
||
194 | * @param ScriptInterface $scriptSig |
||
195 | * @return int |
||
196 | */ |
||
197 | 4 | public function countP2shSigOps(ScriptInterface $scriptSig): int |
|
198 | { |
||
199 | 4 | if (!(new OutputClassifier())->isPayToScriptHash($this)) { |
|
200 | 2 | return $this->countSigOps(true); |
|
201 | } |
||
202 | |||
203 | try { |
||
204 | 4 | $data = null; |
|
205 | 4 | foreach ($scriptSig->getScriptParser() as $exec) { |
|
206 | 4 | if ($exec->getOp() > Opcodes::OP_16) { |
|
207 | 1 | return 0; |
|
208 | } |
||
209 | |||
210 | 3 | if ($exec->isPush()) { |
|
211 | 3 | $data = $exec->getData(); |
|
212 | } |
||
213 | } |
||
214 | |||
215 | 4 | if (!$data instanceof BufferInterface) { |
|
216 | 1 | return 0; |
|
217 | } |
||
218 | |||
219 | 3 | return (new Script($data))->countSigOps(true); |
|
220 | } catch (\Exception $e) { |
||
221 | return 0; |
||
222 | } |
||
223 | } |
||
224 | |||
225 | /** |
||
226 | * @param array|null $ops |
||
227 | * @return bool |
||
228 | */ |
||
229 | 392 | public function isPushOnly(array&$ops = null): bool |
|
230 | { |
||
231 | 392 | $decoded = $this->getScriptParser()->decode(); |
|
232 | 392 | $data = []; |
|
233 | 392 | foreach ($decoded as $entity) { |
|
234 | 336 | if ($entity->getOp() > Opcodes::OP_16) { |
|
235 | 22 | return false; |
|
236 | } |
||
237 | |||
238 | 326 | if ($entity->getOp() === 0) { |
|
239 | 64 | $data[] = new Buffer(); |
|
240 | 64 | continue; |
|
241 | } |
||
242 | |||
243 | 324 | $op = $entity->getOp(); |
|
244 | 324 | if ($op >= Opcodes::OP_1 && $op <= Opcodes::OP_16) { |
|
245 | 39 | $data[] = Number::int(decodeOpN($op))->getBuffer(); |
|
246 | } else { |
||
247 | 323 | $data[] = $entity->getData(); |
|
248 | } |
||
249 | } |
||
250 | 370 | $ops = $data; |
|
251 | 370 | return true; |
|
252 | } |
||
253 | |||
254 | /** |
||
255 | * @param WitnessProgram|null $program |
||
256 | * @return bool |
||
257 | */ |
||
258 | 405 | public function isWitness(& $program = null): bool |
|
259 | { |
||
260 | 405 | $buffer = $this->getBuffer(); |
|
261 | 405 | $size = $buffer->getSize(); |
|
262 | 405 | if ($size < 4 || $size > 42) { |
|
263 | 46 | return false; |
|
264 | } |
||
265 | |||
266 | 379 | $script = $this->getScriptParser()->decode(); |
|
267 | 379 | if (!isset($script[0]) || !isset($script[1])) { |
|
268 | 1 | return false; |
|
269 | } |
||
270 | |||
271 | 378 | $version = $script[0]->getOp(); |
|
272 | 378 | if ($version !== Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) { |
|
273 | 207 | return false; |
|
274 | } |
||
275 | |||
276 | 295 | $witness = $script[1]; |
|
277 | 295 | if ($script[1]->isPush() && $size === $witness->getDataSize() + 2) { |
|
278 | 294 | $program = new WitnessProgram(decodeOpN($version), $witness->getData()); |
|
279 | 294 | return true; |
|
280 | } |
||
281 | |||
282 | 1 | return false; |
|
283 | } |
||
284 | |||
285 | /** |
||
286 | * @param BufferInterface $scriptHash |
||
287 | * @return bool |
||
288 | */ |
||
289 | 7 | public function isP2SH(& $scriptHash): bool |
|
290 | { |
||
291 | 7 | if (strlen($this->script) === 23 |
|
292 | 1 | && $this->script[0] = Opcodes::OP_HASH160 |
|
0 ignored issues
–
show
Comprehensibility
introduced
by
![]() |
|||
293 | 1 | && $this->script[1] = 20 |
|
0 ignored issues
–
show
|
|||
294 | 7 | && $this->script[22] = Opcodes::OP_EQUAL |
|
295 | ) { |
||
296 | 1 | $scriptHash = new Buffer(substr($this->script, 2, 20)); |
|
297 | 1 | return true; |
|
298 | } |
||
299 | |||
300 | 6 | return false; |
|
301 | } |
||
302 | |||
303 | /** |
||
304 | * @param ScriptInterface $script |
||
305 | * @return bool |
||
306 | */ |
||
307 | 255 | public function equals(ScriptInterface $script): bool |
|
308 | { |
||
309 | 255 | return strcmp($this->script, $script->getBinary()) === 0; |
|
310 | } |
||
311 | |||
312 | /** |
||
313 | * @return string |
||
314 | */ |
||
315 | 1 | public function __debugInfo() |
|
316 | { |
||
317 | try { |
||
318 | 1 | $decoded = $this->getScriptParser()->getHumanReadable(); |
|
319 | } catch (\Exception $e) { |
||
320 | $decoded = 'decode failed'; |
||
321 | } |
||
322 | return [ |
||
0 ignored issues
–
show
|
|||
323 | 1 | 'hex' => bin2hex($this->script), |
|
324 | 1 | 'asm' => $decoded |
|
325 | ]; |
||
326 | } |
||
327 | } |
||
328 |