Bit-Wasp /
bitcoin-php
| 1 | <?php |
||||||
| 2 | |||||||
| 3 | declare(strict_types=1); |
||||||
| 4 | |||||||
| 5 | namespace BitWasp\Bitcoin\Script\Interpreter; |
||||||
| 6 | |||||||
| 7 | use BitWasp\Bitcoin\Bitcoin; |
||||||
| 8 | use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface; |
||||||
| 9 | use BitWasp\Bitcoin\Crypto\Hash; |
||||||
| 10 | use BitWasp\Bitcoin\Exceptions\ScriptRuntimeException; |
||||||
| 11 | use BitWasp\Bitcoin\Exceptions\SignatureNotCanonical; |
||||||
| 12 | use BitWasp\Bitcoin\Script\Classifier\OutputClassifier; |
||||||
| 13 | use BitWasp\Bitcoin\Script\Opcodes; |
||||||
| 14 | use BitWasp\Bitcoin\Script\Script; |
||||||
| 15 | use BitWasp\Bitcoin\Script\ScriptFactory; |
||||||
| 16 | use BitWasp\Bitcoin\Script\ScriptInterface; |
||||||
| 17 | use BitWasp\Bitcoin\Script\ScriptWitness; |
||||||
| 18 | use BitWasp\Bitcoin\Script\ScriptWitnessInterface; |
||||||
| 19 | use BitWasp\Bitcoin\Script\WitnessProgram; |
||||||
| 20 | use BitWasp\Bitcoin\Signature\TransactionSignature; |
||||||
| 21 | use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash; |
||||||
| 22 | use BitWasp\Bitcoin\Transaction\TransactionInputInterface; |
||||||
| 23 | use BitWasp\Buffertools\Buffer; |
||||||
| 24 | use BitWasp\Buffertools\BufferInterface; |
||||||
| 25 | |||||||
| 26 | class Interpreter implements InterpreterInterface |
||||||
| 27 | { |
||||||
| 28 | |||||||
| 29 | /** |
||||||
| 30 | * @var \BitWasp\Bitcoin\Math\Math |
||||||
| 31 | */ |
||||||
| 32 | private $math; |
||||||
| 33 | |||||||
| 34 | /** |
||||||
| 35 | * @var BufferInterface |
||||||
| 36 | */ |
||||||
| 37 | private $vchFalse; |
||||||
| 38 | |||||||
| 39 | /** |
||||||
| 40 | * @var BufferInterface |
||||||
| 41 | */ |
||||||
| 42 | private $vchTrue; |
||||||
| 43 | |||||||
| 44 | /** |
||||||
| 45 | * @var array |
||||||
| 46 | */ |
||||||
| 47 | private $disabledOps = [ |
||||||
| 48 | Opcodes::OP_CAT, Opcodes::OP_SUBSTR, Opcodes::OP_LEFT, Opcodes::OP_RIGHT, |
||||||
| 49 | Opcodes::OP_INVERT, Opcodes::OP_AND, Opcodes::OP_OR, Opcodes::OP_XOR, |
||||||
| 50 | Opcodes::OP_2MUL, Opcodes::OP_2DIV, Opcodes::OP_MUL, Opcodes::OP_DIV, |
||||||
| 51 | Opcodes::OP_MOD, Opcodes::OP_LSHIFT, Opcodes::OP_RSHIFT |
||||||
| 52 | ]; |
||||||
| 53 | |||||||
| 54 | /** |
||||||
| 55 | * @param EcAdapterInterface $ecAdapter |
||||||
| 56 | */ |
||||||
| 57 | 2468 | public function __construct(EcAdapterInterface $ecAdapter = null) |
|||||
| 58 | { |
||||||
| 59 | 2468 | $ecAdapter = $ecAdapter ?: Bitcoin::getEcAdapter(); |
|||||
| 60 | 2468 | $this->math = $ecAdapter->getMath(); |
|||||
| 61 | 2468 | $this->vchFalse = new Buffer("", 0); |
|||||
| 62 | 2468 | $this->vchTrue = new Buffer("\x01", 1); |
|||||
| 63 | 2468 | } |
|||||
| 64 | |||||||
| 65 | /** |
||||||
| 66 | * Cast the value to a boolean |
||||||
| 67 | * |
||||||
| 68 | * @param BufferInterface $value |
||||||
| 69 | * @return bool |
||||||
| 70 | */ |
||||||
| 71 | 3518 | public function castToBool(BufferInterface $value): bool |
|||||
| 72 | { |
||||||
| 73 | 3518 | $val = $value->getBinary(); |
|||||
| 74 | 3518 | for ($i = 0, $size = strlen($val); $i < $size; $i++) { |
|||||
| 75 | 3237 | $chr = ord($val[$i]); |
|||||
| 76 | 3237 | if ($chr !== 0) { |
|||||
| 77 | 3236 | if (($i === ($size - 1)) && $chr === 0x80) { |
|||||
| 78 | return false; |
||||||
| 79 | } |
||||||
| 80 | 3236 | return true; |
|||||
| 81 | } |
||||||
| 82 | } |
||||||
| 83 | 867 | return false; |
|||||
| 84 | } |
||||||
| 85 | |||||||
| 86 | /** |
||||||
| 87 | * @param BufferInterface $signature |
||||||
| 88 | * @return bool |
||||||
| 89 | */ |
||||||
| 90 | public function isValidSignatureEncoding(BufferInterface $signature): bool |
||||||
| 91 | { |
||||||
| 92 | try { |
||||||
| 93 | TransactionSignature::isDERSignature($signature); |
||||||
| 94 | return true; |
||||||
| 95 | } catch (SignatureNotCanonical $e) { |
||||||
| 96 | /* In any case, we will return false outside this block */ |
||||||
| 97 | } |
||||||
| 98 | |||||||
| 99 | return false; |
||||||
| 100 | } |
||||||
| 101 | |||||||
| 102 | /** |
||||||
| 103 | * @param int $opCode |
||||||
| 104 | * @param BufferInterface $pushData |
||||||
| 105 | * @return bool |
||||||
| 106 | * @throws \Exception |
||||||
| 107 | */ |
||||||
| 108 | 461 | public function checkMinimalPush($opCode, BufferInterface $pushData): bool |
|||||
| 109 | { |
||||||
| 110 | 461 | $pushSize = $pushData->getSize(); |
|||||
| 111 | 461 | $binary = $pushData->getBinary(); |
|||||
| 112 | |||||||
| 113 | 461 | if ($pushSize === 0) { |
|||||
| 114 | 217 | return $opCode === Opcodes::OP_0; |
|||||
| 115 | 377 | } elseif ($pushSize === 1) { |
|||||
| 116 | 89 | $first = ord($binary[0]); |
|||||
| 117 | |||||||
| 118 | 89 | if ($first >= 1 && $first <= 16) { |
|||||
| 119 | 65 | return $opCode === (Opcodes::OP_1 + ($first - 1)); |
|||||
| 120 | 25 | } elseif ($first === 0x81) { |
|||||
| 121 | 25 | return $opCode === Opcodes::OP_1NEGATE; |
|||||
| 122 | } |
||||||
| 123 | 289 | } elseif ($pushSize <= 75) { |
|||||
| 124 | 281 | return $opCode === $pushSize; |
|||||
| 125 | 9 | } elseif ($pushSize <= 255) { |
|||||
| 126 | 5 | return $opCode === Opcodes::OP_PUSHDATA1; |
|||||
| 127 | 5 | } elseif ($pushSize <= 65535) { |
|||||
| 128 | 5 | return $opCode === Opcodes::OP_PUSHDATA2; |
|||||
| 129 | } |
||||||
| 130 | |||||||
| 131 | 21 | return true; |
|||||
| 132 | } |
||||||
| 133 | |||||||
| 134 | /** |
||||||
| 135 | * @param int $count |
||||||
| 136 | * @return $this |
||||||
| 137 | */ |
||||||
| 138 | 4409 | private function checkOpcodeCount(int $count) |
|||||
| 139 | { |
||||||
| 140 | 4409 | if ($count > 201) { |
|||||
| 141 | 20 | throw new \RuntimeException('Error: Script op code count'); |
|||||
| 142 | } |
||||||
| 143 | |||||||
| 144 | 4409 | return $this; |
|||||
| 145 | } |
||||||
| 146 | |||||||
| 147 | /** |
||||||
| 148 | * @param WitnessProgram $witnessProgram |
||||||
| 149 | * @param ScriptWitnessInterface $scriptWitness |
||||||
| 150 | * @param int $flags |
||||||
| 151 | * @param CheckerBase $checker |
||||||
| 152 | * @return bool |
||||||
| 153 | */ |
||||||
| 154 | 268 | private function verifyWitnessProgram(WitnessProgram $witnessProgram, ScriptWitnessInterface $scriptWitness, int $flags, CheckerBase $checker): bool |
|||||
| 155 | { |
||||||
| 156 | 268 | $witnessCount = count($scriptWitness); |
|||||
| 157 | |||||||
| 158 | 268 | if ($witnessProgram->getVersion() === 0) { |
|||||
| 159 | 264 | $buffer = $witnessProgram->getProgram(); |
|||||
| 160 | 264 | if ($buffer->getSize() === 32) { |
|||||
| 161 | // Version 0 segregated witness program: SHA256(Script) in program, Script + inputs in witness |
||||||
| 162 | 232 | if ($witnessCount === 0) { |
|||||
| 163 | // Must contain script at least |
||||||
| 164 | 4 | return false; |
|||||
| 165 | } |
||||||
| 166 | |||||||
| 167 | 228 | $scriptPubKey = new Script($scriptWitness[$witnessCount - 1]); |
|||||
| 168 | 228 | $stackValues = $scriptWitness->slice(0, -1); |
|||||
| 169 | 228 | if (!$buffer->equals($scriptPubKey->getWitnessScriptHash())) { |
|||||
| 170 | 228 | return false; |
|||||
| 171 | } |
||||||
| 172 | 32 | } elseif ($buffer->getSize() === 20) { |
|||||
| 173 | // Version 0 special case for pay-to-pubkeyhash |
||||||
| 174 | 28 | if ($witnessCount !== 2) { |
|||||
| 175 | // 2 items in witness - <signature> <pubkey> |
||||||
| 176 | 4 | return false; |
|||||
| 177 | } |
||||||
| 178 | |||||||
| 179 | 24 | $scriptPubKey = ScriptFactory::scriptPubKey()->payToPubKeyHash($buffer); |
|||||
| 180 | 24 | $stackValues = $scriptWitness; |
|||||
| 181 | } else { |
||||||
| 182 | 248 | return false; |
|||||
| 183 | } |
||||||
| 184 | 4 | } elseif ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM) { |
|||||
| 185 | 4 | return false; |
|||||
| 186 | } else { |
||||||
| 187 | // Unknown versions are always 'valid' to permit future soft forks |
||||||
| 188 | return true; |
||||||
| 189 | } |
||||||
| 190 | |||||||
| 191 | 244 | $mainStack = new Stack(); |
|||||
| 192 | 244 | foreach ($stackValues as $value) { |
|||||
| 193 | 208 | $mainStack->push($value); |
|||||
| 194 | } |
||||||
| 195 | |||||||
| 196 | 244 | if (!$this->evaluate($scriptPubKey, $mainStack, SigHash::V1, $flags, $checker)) { |
|||||
| 197 | 80 | return false; |
|||||
| 198 | } |
||||||
| 199 | |||||||
| 200 | 164 | if ($mainStack->count() !== 1) { |
|||||
| 201 | 56 | return false; |
|||||
| 202 | } |
||||||
| 203 | |||||||
| 204 | 108 | if (!$this->castToBool($mainStack->bottom())) { |
|||||
| 205 | 36 | return false; |
|||||
| 206 | } |
||||||
| 207 | |||||||
| 208 | 72 | return true; |
|||||
| 209 | } |
||||||
| 210 | |||||||
| 211 | /** |
||||||
| 212 | * @param ScriptInterface $scriptSig |
||||||
| 213 | * @param ScriptInterface $scriptPubKey |
||||||
| 214 | * @param int $flags |
||||||
| 215 | * @param CheckerBase $checker |
||||||
| 216 | * @param ScriptWitnessInterface|null $witness |
||||||
| 217 | * @return bool |
||||||
| 218 | */ |
||||||
| 219 | 4667 | public function verify(ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, int $flags, CheckerBase $checker, ScriptWitnessInterface $witness = null): bool |
|||||
| 220 | { |
||||||
| 221 | 4667 | static $emptyWitness = null; |
|||||
| 222 | 4667 | if ($emptyWitness === null) { |
|||||
| 223 | 1 | $emptyWitness = new ScriptWitness(); |
|||||
| 224 | } |
||||||
| 225 | |||||||
| 226 | 4667 | $witness = is_null($witness) ? $emptyWitness : $witness; |
|||||
| 227 | |||||||
| 228 | 4667 | if (($flags & self::VERIFY_SIGPUSHONLY) !== 0 && !$scriptSig->isPushOnly()) { |
|||||
| 229 | 8 | return false; |
|||||
| 230 | } |
||||||
| 231 | |||||||
| 232 | 4659 | $stack = new Stack(); |
|||||
| 233 | 4659 | if (!$this->evaluate($scriptSig, $stack, SigHash::V0, $flags, $checker)) { |
|||||
| 234 | 221 | return false; |
|||||
| 235 | } |
||||||
| 236 | |||||||
| 237 | 4438 | $backup = []; |
|||||
| 238 | 4438 | if ($flags & self::VERIFY_P2SH) { |
|||||
| 239 | 3315 | foreach ($stack as $s) { |
|||||
| 240 | 2646 | $backup[] = $s; |
|||||
| 241 | } |
||||||
| 242 | } |
||||||
| 243 | |||||||
| 244 | 4438 | if (!$this->evaluate($scriptPubKey, $stack, SigHash::V0, $flags, $checker)) { |
|||||
| 245 | 1342 | return false; |
|||||
| 246 | } |
||||||
| 247 | |||||||
| 248 | 3096 | if ($stack->isEmpty()) { |
|||||
| 249 | 61 | return false; |
|||||
| 250 | } |
||||||
| 251 | |||||||
| 252 | 3035 | if (false === $this->castToBool($stack[-1])) { |
|||||
| 253 | 185 | return false; |
|||||
| 254 | } |
||||||
| 255 | |||||||
| 256 | 2850 | $program = null; |
|||||
| 257 | 2850 | if ($flags & self::VERIFY_WITNESS) { |
|||||
| 258 | 348 | if ($scriptPubKey->isWitness($program)) { |
|||||
| 259 | /** @var WitnessProgram $program */ |
||||||
| 260 | 152 | if ($scriptSig->getBuffer()->getSize() !== 0) { |
|||||
| 261 | 4 | return false; |
|||||
| 262 | } |
||||||
| 263 | |||||||
| 264 | 148 | if (!$this->verifyWitnessProgram($program, $witness, $flags, $checker)) { |
|||||
| 265 | 112 | return false; |
|||||
| 266 | } |
||||||
| 267 | |||||||
| 268 | 36 | $stack->resize(1); |
|||||
| 269 | } |
||||||
| 270 | } |
||||||
| 271 | |||||||
| 272 | 2734 | if ($flags & self::VERIFY_P2SH && (new OutputClassifier())->isPayToScriptHash($scriptPubKey)) { |
|||||
| 273 | 246 | if (!$scriptSig->isPushOnly()) { |
|||||
| 274 | 13 | return false; |
|||||
| 275 | } |
||||||
| 276 | |||||||
| 277 | 233 | $stack = new Stack(); |
|||||
| 278 | 233 | foreach ($backup as $i) { |
|||||
| 279 | 233 | $stack->push($i); |
|||||
| 280 | } |
||||||
| 281 | |||||||
| 282 | // Restore mainStack to how it was after evaluating scriptSig |
||||||
| 283 | 233 | if ($stack->isEmpty()) { |
|||||
| 284 | return false; |
||||||
| 285 | } |
||||||
| 286 | |||||||
| 287 | // Load redeemscript as the scriptPubKey |
||||||
| 288 | 233 | $scriptPubKey = new Script($stack->bottom()); |
|||||
| 289 | 233 | $stack->pop(); |
|||||
| 290 | |||||||
| 291 | 233 | if (!$this->evaluate($scriptPubKey, $stack, 0, $flags, $checker)) { |
|||||
| 292 | 25 | return false; |
|||||
| 293 | } |
||||||
| 294 | |||||||
| 295 | 208 | if ($stack->isEmpty()) { |
|||||
| 296 | 20 | return false; |
|||||
| 297 | } |
||||||
| 298 | |||||||
| 299 | 188 | if (!$this->castToBool($stack->bottom())) { |
|||||
| 300 | 4 | return false; |
|||||
| 301 | } |
||||||
| 302 | |||||||
| 303 | 184 | if ($flags & self::VERIFY_WITNESS) { |
|||||
| 304 | 144 | if ($scriptPubKey->isWitness($program)) { |
|||||
| 305 | /** @var WitnessProgram $program */ |
||||||
| 306 | 124 | if (!$scriptSig->equals(ScriptFactory::sequence([$scriptPubKey->getBuffer()]))) { |
|||||
| 307 | 4 | return false; // SCRIPT_ERR_WITNESS_MALLEATED_P2SH |
|||||
| 308 | } |
||||||
| 309 | |||||||
| 310 | 120 | if (!$this->verifyWitnessProgram($program, $witness, $flags, $checker)) { |
|||||
| 311 | 84 | return false; |
|||||
| 312 | } |
||||||
| 313 | |||||||
| 314 | 36 | $stack->resize(1); |
|||||
| 315 | } |
||||||
| 316 | } |
||||||
| 317 | } |
||||||
| 318 | |||||||
| 319 | 2584 | if ($flags & self::VERIFY_CLEAN_STACK) { |
|||||
| 320 | 12 | if (!($flags & self::VERIFY_P2SH !== 0) && ($flags & self::VERIFY_WITNESS !== 0)) { |
|||||
| 321 | return false; // implied flags required |
||||||
| 322 | } |
||||||
| 323 | |||||||
| 324 | 12 | if (count($stack) !== 1) { |
|||||
| 325 | 8 | return false; // Cleanstack |
|||||
| 326 | } |
||||||
| 327 | } |
||||||
| 328 | |||||||
| 329 | 2576 | if ($flags & self::VERIFY_WITNESS) { |
|||||
| 330 | 116 | if (!$flags & self::VERIFY_P2SH) { |
|||||
| 331 | return false; // |
||||||
| 332 | } |
||||||
| 333 | |||||||
| 334 | 116 | if ($program === null && !$witness->isNull()) { |
|||||
| 335 | 4 | return false; // SCRIPT_ERR_WITNESS_UNEXPECTED |
|||||
| 336 | } |
||||||
| 337 | } |
||||||
| 338 | |||||||
| 339 | 2572 | return true; |
|||||
| 340 | } |
||||||
| 341 | |||||||
| 342 | /** |
||||||
| 343 | * @param Stack $vfStack |
||||||
| 344 | * @param bool $value |
||||||
| 345 | * @return bool |
||||||
| 346 | */ |
||||||
| 347 | 4760 | public function checkExec(Stack $vfStack, bool $value): bool |
|||||
| 348 | { |
||||||
| 349 | 4760 | $ret = 0; |
|||||
| 350 | 4760 | foreach ($vfStack as $item) { |
|||||
| 351 | 1171 | if ($item === $value) { |
|||||
| 352 | 738 | $ret++; |
|||||
| 353 | } |
||||||
| 354 | } |
||||||
| 355 | |||||||
| 356 | 4760 | return (bool) $ret; |
|||||
| 357 | } |
||||||
| 358 | |||||||
| 359 | /** |
||||||
| 360 | * @param ScriptInterface $script |
||||||
| 361 | * @param Stack $mainStack |
||||||
| 362 | * @param int $sigVersion |
||||||
| 363 | * @param int $flags |
||||||
| 364 | * @param CheckerBase $checker |
||||||
| 365 | * @return bool |
||||||
| 366 | */ |
||||||
| 367 | 4660 | public function evaluate(ScriptInterface $script, Stack $mainStack, int $sigVersion, int $flags, CheckerBase $checker): bool |
|||||
| 368 | { |
||||||
| 369 | 4660 | $hashStartPos = 0; |
|||||
| 370 | 4660 | $opCount = 0; |
|||||
| 371 | 4660 | $zero = gmp_init(0, 10); |
|||||
| 372 | 4660 | $altStack = new Stack(); |
|||||
| 373 | 4660 | $vfStack = new Stack(); |
|||||
| 374 | 4660 | $minimal = ($flags & self::VERIFY_MINIMALDATA) !== 0; |
|||||
| 375 | 4660 | $parser = $script->getScriptParser(); |
|||||
| 376 | |||||||
| 377 | 4660 | if ($script->getBuffer()->getSize() > 10000) { |
|||||
| 378 | 4 | return false; |
|||||
| 379 | } |
||||||
| 380 | |||||||
| 381 | try { |
||||||
| 382 | 4660 | foreach ($parser as $operation) { |
|||||
| 383 | 4643 | $opCode = $operation->getOp(); |
|||||
| 384 | 4643 | $pushData = $operation->getData(); |
|||||
| 385 | 4643 | $fExec = !$this->checkExec($vfStack, false); |
|||||
| 386 | |||||||
| 387 | // If pushdata was written to |
||||||
| 388 | 4643 | if ($operation->isPush() && $operation->getDataSize() > InterpreterInterface::MAX_SCRIPT_ELEMENT_SIZE) { |
|||||
| 389 | 9 | throw new \RuntimeException('Error - push size'); |
|||||
| 390 | } |
||||||
| 391 | |||||||
| 392 | // OP_RESERVED should not count towards opCount |
||||||
| 393 | 4642 | if ($opCode > Opcodes::OP_16 && ++$opCount) { |
|||||
| 394 | 4409 | $this->checkOpcodeCount($opCount); |
|||||
| 395 | } |
||||||
| 396 | |||||||
| 397 | 4642 | if (in_array($opCode, $this->disabledOps, true)) { |
|||||
| 398 | 96 | throw new \RuntimeException('Disabled Opcode'); |
|||||
| 399 | } |
||||||
| 400 | |||||||
| 401 | 4642 | if ($fExec && $operation->isPush()) { |
|||||
| 402 | // In range of a pushdata opcode |
||||||
| 403 | 3412 | if ($minimal && !$this->checkMinimalPush($opCode, $pushData)) { |
|||||
| 404 | 84 | throw new ScriptRuntimeException(self::VERIFY_MINIMALDATA, 'Minimal pushdata required'); |
|||||
| 405 | } |
||||||
| 406 | |||||||
| 407 | 3328 | $mainStack->push($pushData); |
|||||
| 408 | // echo " - [pushed '" . $pushData->getHex() . "']\n"; |
||||||
| 409 | 4501 | } elseif ($fExec || (Opcodes::OP_IF <= $opCode && $opCode <= Opcodes::OP_ENDIF)) { |
|||||
| 410 | // echo "OPCODE - " . $script->getOpcodes()->getOp($opCode) . "\n"; |
||||||
| 411 | 4501 | switch ($opCode) { |
|||||
| 412 | case Opcodes::OP_1NEGATE: |
||||||
| 413 | case Opcodes::OP_1: |
||||||
| 414 | case Opcodes::OP_2: |
||||||
| 415 | case Opcodes::OP_3: |
||||||
| 416 | case Opcodes::OP_4: |
||||||
| 417 | case Opcodes::OP_5: |
||||||
| 418 | case Opcodes::OP_6: |
||||||
| 419 | case Opcodes::OP_7: |
||||||
| 420 | case Opcodes::OP_8: |
||||||
| 421 | case Opcodes::OP_9: |
||||||
| 422 | case Opcodes::OP_10: |
||||||
| 423 | case Opcodes::OP_11: |
||||||
| 424 | case Opcodes::OP_12: |
||||||
| 425 | case Opcodes::OP_13: |
||||||
| 426 | case Opcodes::OP_14: |
||||||
| 427 | case Opcodes::OP_15: |
||||||
| 428 | case Opcodes::OP_16: |
||||||
| 429 | 2832 | $num = \BitWasp\Bitcoin\Script\decodeOpN($opCode); |
|||||
| 430 | 2832 | $mainStack->push(Number::int($num)->getBuffer()); |
|||||
| 431 | 2832 | break; |
|||||
| 432 | |||||||
| 433 | case Opcodes::OP_CHECKLOCKTIMEVERIFY: |
||||||
| 434 | 24 | if (!($flags & self::VERIFY_CHECKLOCKTIMEVERIFY)) { |
|||||
| 435 | 24 | if ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { |
|||||
| 436 | 4 | throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); |
|||||
| 437 | } |
||||||
| 438 | 20 | break; |
|||||
| 439 | } |
||||||
| 440 | |||||||
| 441 | if ($mainStack->isEmpty()) { |
||||||
| 442 | throw new \RuntimeException('Invalid stack operation - CLTV'); |
||||||
| 443 | } |
||||||
| 444 | |||||||
| 445 | $lockTime = Number::buffer($mainStack[-1], $minimal, 5, $this->math); |
||||||
| 446 | if (!$checker->checkLockTime($lockTime)) { |
||||||
| 447 | throw new ScriptRuntimeException(self::VERIFY_CHECKLOCKTIMEVERIFY, 'Unsatisfied locktime'); |
||||||
| 448 | } |
||||||
| 449 | |||||||
| 450 | break; |
||||||
| 451 | |||||||
| 452 | case Opcodes::OP_CHECKSEQUENCEVERIFY: |
||||||
| 453 | 48 | if (!($flags & self::VERIFY_CHECKSEQUENCEVERIFY)) { |
|||||
| 454 | 24 | if ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { |
|||||
| 455 | 4 | throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); |
|||||
| 456 | } |
||||||
| 457 | 20 | break; |
|||||
| 458 | } |
||||||
| 459 | |||||||
| 460 | 24 | if ($mainStack->isEmpty()) { |
|||||
| 461 | 4 | throw new \RuntimeException('Invalid stack operation - CSV'); |
|||||
| 462 | } |
||||||
| 463 | |||||||
| 464 | 20 | $sequence = Number::buffer($mainStack[-1], $minimal, 5, $this->math); |
|||||
| 465 | 16 | $nSequence = $sequence->getGmp(); |
|||||
| 466 | 16 | if ($this->math->cmp($nSequence, $zero) < 0) { |
|||||
|
0 ignored issues
–
show
Bug
introduced
by
Loading history...
|
|||||||
| 467 | 4 | throw new ScriptRuntimeException(self::VERIFY_CHECKSEQUENCEVERIFY, 'Negative locktime'); |
|||||
| 468 | } |
||||||
| 469 | |||||||
| 470 | 12 | if ($this->math->cmp($this->math->bitwiseAnd($nSequence, gmp_init(TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG, 10)), $zero) !== 0) { |
|||||
|
0 ignored issues
–
show
It seems like
gmp_init(BitWasp\Bitcoin...KTIME_DISABLE_FLAG, 10) can also be of type resource; however, parameter $other of Mdanter\Ecc\Math\GmpMath::bitwiseAnd() does only seem to accept GMP, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 471 | 4 | break; |
|||||
| 472 | } |
||||||
| 473 | |||||||
| 474 | 8 | if (!$checker->checkSequence($sequence)) { |
|||||
| 475 | 8 | throw new ScriptRuntimeException(self::VERIFY_CHECKSEQUENCEVERIFY, 'Unsatisfied sequence'); |
|||||
| 476 | } |
||||||
| 477 | break; |
||||||
| 478 | |||||||
| 479 | case Opcodes::OP_NOP1: |
||||||
| 480 | case Opcodes::OP_NOP4: |
||||||
| 481 | case Opcodes::OP_NOP5: |
||||||
| 482 | case Opcodes::OP_NOP6: |
||||||
| 483 | case Opcodes::OP_NOP7: |
||||||
| 484 | case Opcodes::OP_NOP8: |
||||||
| 485 | case Opcodes::OP_NOP9: |
||||||
| 486 | case Opcodes::OP_NOP10: |
||||||
| 487 | 108 | if ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { |
|||||
| 488 | 40 | throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); |
|||||
| 489 | } |
||||||
| 490 | 68 | break; |
|||||
| 491 | |||||||
| 492 | case Opcodes::OP_NOP: |
||||||
| 493 | 336 | break; |
|||||
| 494 | |||||||
| 495 | case Opcodes::OP_IF: |
||||||
| 496 | case Opcodes::OP_NOTIF: |
||||||
| 497 | // <expression> if [statements] [else [statements]] endif |
||||||
| 498 | 1268 | $value = false; |
|||||
| 499 | 1268 | if ($fExec) { |
|||||
| 500 | 1268 | if ($mainStack->isEmpty()) { |
|||||
| 501 | 48 | throw new \RuntimeException('Unbalanced conditional'); |
|||||
| 502 | } |
||||||
| 503 | 1220 | $vch = $mainStack[-1]; |
|||||
| 504 | |||||||
| 505 | 1220 | if ($sigVersion === SigHash::V1 && ($flags & self::VERIFY_MINIMALIF)) { |
|||||
| 506 | 80 | if ($vch->getSize() > 1) { |
|||||
| 507 | 16 | throw new ScriptRuntimeException(self::VERIFY_MINIMALIF, 'Input to OP_IF/NOTIF should be minimally encoded'); |
|||||
| 508 | } |
||||||
| 509 | |||||||
| 510 | 64 | if ($vch->getSize() === 1 && $vch->getBinary() !== "\x01") { |
|||||
| 511 | 32 | throw new ScriptRuntimeException(self::VERIFY_MINIMALIF, 'Input to OP_IF/NOTIF should be minimally encoded'); |
|||||
| 512 | } |
||||||
| 513 | } |
||||||
| 514 | |||||||
| 515 | 1172 | $buffer = Number::buffer($mainStack->pop(), $minimal)->getBuffer(); |
|||||
| 516 | 1172 | $value = $this->castToBool($buffer); |
|||||
| 517 | 1172 | if ($opCode === Opcodes::OP_NOTIF) { |
|||||
| 518 | 164 | $value = !$value; |
|||||
| 519 | } |
||||||
| 520 | } |
||||||
| 521 | 1172 | $vfStack->push($value); |
|||||
| 522 | 1172 | break; |
|||||
| 523 | |||||||
| 524 | case Opcodes::OP_ELSE: |
||||||
| 525 | 460 | if ($vfStack->isEmpty()) { |
|||||
| 526 | 16 | throw new \RuntimeException('Unbalanced conditional'); |
|||||
| 527 | } |
||||||
| 528 | 452 | $vfStack->push(!$vfStack->pop()); |
|||||
| 529 | 452 | break; |
|||||
| 530 | |||||||
| 531 | case Opcodes::OP_ENDIF: |
||||||
| 532 | 780 | if ($vfStack->isEmpty()) { |
|||||
| 533 | 28 | throw new \RuntimeException('Unbalanced conditional'); |
|||||
| 534 | } |
||||||
| 535 | 764 | $vfStack->pop(); |
|||||
| 536 | 764 | break; |
|||||
| 537 | |||||||
| 538 | case Opcodes::OP_VERIFY: |
||||||
| 539 | 120 | if ($mainStack->isEmpty()) { |
|||||
| 540 | 4 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 541 | } |
||||||
| 542 | 116 | $value = $this->castToBool($mainStack[-1]); |
|||||
| 543 | 116 | if (!$value) { |
|||||
| 544 | 4 | throw new \RuntimeException('Error: verify'); |
|||||
| 545 | } |
||||||
| 546 | 112 | $mainStack->pop(); |
|||||
| 547 | 112 | break; |
|||||
| 548 | |||||||
| 549 | case Opcodes::OP_TOALTSTACK: |
||||||
| 550 | 32 | if ($mainStack->isEmpty()) { |
|||||
| 551 | 4 | throw new \RuntimeException('Invalid stack operation OP_TOALTSTACK'); |
|||||
| 552 | } |
||||||
| 553 | 28 | $altStack->push($mainStack->pop()); |
|||||
| 554 | 28 | break; |
|||||
| 555 | |||||||
| 556 | case Opcodes::OP_FROMALTSTACK: |
||||||
| 557 | 20 | if ($altStack->isEmpty()) { |
|||||
| 558 | 8 | throw new \RuntimeException('Invalid alt-stack operation OP_FROMALTSTACK'); |
|||||
| 559 | } |
||||||
| 560 | 12 | $mainStack->push($altStack->pop()); |
|||||
| 561 | 12 | break; |
|||||
| 562 | |||||||
| 563 | case Opcodes::OP_IFDUP: |
||||||
| 564 | // If top value not zero, duplicate it. |
||||||
| 565 | 24 | if ($mainStack->isEmpty()) { |
|||||
| 566 | 8 | throw new \RuntimeException('Invalid stack operation OP_IFDUP'); |
|||||
| 567 | } |
||||||
| 568 | 16 | $vch = $mainStack[-1]; |
|||||
| 569 | 16 | if ($this->castToBool($vch)) { |
|||||
| 570 | 12 | $mainStack->push($vch); |
|||||
| 571 | } |
||||||
| 572 | 16 | break; |
|||||
| 573 | |||||||
| 574 | case Opcodes::OP_DEPTH: |
||||||
| 575 | 301 | $num = count($mainStack); |
|||||
| 576 | 301 | $depth = Number::int($num)->getBuffer(); |
|||||
| 577 | 301 | $mainStack->push($depth); |
|||||
| 578 | 301 | break; |
|||||
| 579 | |||||||
| 580 | case Opcodes::OP_DROP: |
||||||
| 581 | 192 | if ($mainStack->isEmpty()) { |
|||||
| 582 | 8 | throw new \RuntimeException('Invalid stack operation OP_DROP'); |
|||||
| 583 | } |
||||||
| 584 | 184 | $mainStack->pop(); |
|||||
| 585 | 184 | break; |
|||||
| 586 | |||||||
| 587 | case Opcodes::OP_DUP: |
||||||
| 588 | 112 | if ($mainStack->isEmpty()) { |
|||||
| 589 | 8 | throw new \RuntimeException('Invalid stack operation OP_DUP'); |
|||||
| 590 | } |
||||||
| 591 | 104 | $vch = $mainStack[-1]; |
|||||
| 592 | 104 | $mainStack->push($vch); |
|||||
| 593 | 104 | break; |
|||||
| 594 | |||||||
| 595 | case Opcodes::OP_NIP: |
||||||
| 596 | 24 | if (count($mainStack) < 2) { |
|||||
| 597 | 12 | throw new \RuntimeException('Invalid stack operation OP_NIP'); |
|||||
| 598 | } |
||||||
| 599 | 12 | unset($mainStack[-2]); |
|||||
| 600 | 12 | break; |
|||||
| 601 | |||||||
| 602 | case Opcodes::OP_OVER: |
||||||
| 603 | 24 | if (count($mainStack) < 2) { |
|||||
| 604 | 12 | throw new \RuntimeException('Invalid stack operation OP_OVER'); |
|||||
| 605 | } |
||||||
| 606 | 12 | $vch = $mainStack[-2]; |
|||||
| 607 | 12 | $mainStack->push($vch); |
|||||
| 608 | 12 | break; |
|||||
| 609 | |||||||
| 610 | case Opcodes::OP_ROT: |
||||||
| 611 | 48 | if (count($mainStack) < 3) { |
|||||
| 612 | 16 | throw new \RuntimeException('Invalid stack operation OP_ROT'); |
|||||
| 613 | } |
||||||
| 614 | 32 | $mainStack->swap(-3, -2); |
|||||
| 615 | 32 | $mainStack->swap(-2, -1); |
|||||
| 616 | 32 | break; |
|||||
| 617 | |||||||
| 618 | case Opcodes::OP_SWAP: |
||||||
| 619 | 40 | if (count($mainStack) < 2) { |
|||||
| 620 | 12 | throw new \RuntimeException('Invalid stack operation OP_SWAP'); |
|||||
| 621 | } |
||||||
| 622 | 28 | $mainStack->swap(-2, -1); |
|||||
| 623 | 28 | break; |
|||||
| 624 | |||||||
| 625 | case Opcodes::OP_TUCK: |
||||||
| 626 | 24 | if (count($mainStack) < 2) { |
|||||
| 627 | 12 | throw new \RuntimeException('Invalid stack operation OP_TUCK'); |
|||||
| 628 | } |
||||||
| 629 | 12 | $vch = $mainStack[-1]; |
|||||
| 630 | 12 | $mainStack->add(- 2, $vch); |
|||||
| 631 | 12 | break; |
|||||
| 632 | |||||||
| 633 | case Opcodes::OP_PICK: |
||||||
| 634 | case Opcodes::OP_ROLL: |
||||||
| 635 | 116 | if (count($mainStack) < 2) { |
|||||
| 636 | 16 | throw new \RuntimeException('Invalid stack operation OP_PICK'); |
|||||
| 637 | } |
||||||
| 638 | |||||||
| 639 | 100 | $n = Number::buffer($mainStack[-1], $minimal, 4)->getGmp(); |
|||||
| 640 | 92 | $mainStack->pop(); |
|||||
| 641 | 92 | if ($this->math->cmp($n, $zero) < 0 || $this->math->cmp($n, gmp_init(count($mainStack))) >= 0) { |
|||||
| 642 | 20 | throw new \RuntimeException('Invalid stack operation OP_PICK'); |
|||||
| 643 | } |
||||||
| 644 | |||||||
| 645 | 72 | $pos = (int) gmp_strval($this->math->sub($this->math->sub($zero, $n), gmp_init(1)), 10); |
|||||
|
0 ignored issues
–
show
It seems like
gmp_init(1) can also be of type resource; however, parameter $subtrahend of Mdanter\Ecc\Math\GmpMath::sub() does only seem to accept GMP, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
It seems like
$zero can also be of type resource; however, parameter $minuend of Mdanter\Ecc\Math\GmpMath::sub() does only seem to accept GMP, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 646 | 72 | $vch = $mainStack[$pos]; |
|||||
| 647 | 72 | if ($opCode === Opcodes::OP_ROLL) { |
|||||
| 648 | 36 | unset($mainStack[$pos]); |
|||||
| 649 | } |
||||||
| 650 | 72 | $mainStack->push($vch); |
|||||
| 651 | 72 | break; |
|||||
| 652 | |||||||
| 653 | case Opcodes::OP_2DROP: |
||||||
| 654 | 36 | if (count($mainStack) < 2) { |
|||||
| 655 | 4 | throw new \RuntimeException('Invalid stack operation OP_2DROP'); |
|||||
| 656 | } |
||||||
| 657 | 32 | $mainStack->pop(); |
|||||
| 658 | 32 | $mainStack->pop(); |
|||||
| 659 | 32 | break; |
|||||
| 660 | |||||||
| 661 | case Opcodes::OP_2DUP: |
||||||
| 662 | 24 | if (count($mainStack) < 2) { |
|||||
| 663 | 12 | throw new \RuntimeException('Invalid stack operation OP_2DUP'); |
|||||
| 664 | } |
||||||
| 665 | 12 | $string1 = $mainStack[-2]; |
|||||
| 666 | 12 | $string2 = $mainStack[-1]; |
|||||
| 667 | 12 | $mainStack->push($string1); |
|||||
| 668 | 12 | $mainStack->push($string2); |
|||||
| 669 | 12 | break; |
|||||
| 670 | |||||||
| 671 | case Opcodes::OP_3DUP: |
||||||
| 672 | 44 | if (count($mainStack) < 3) { |
|||||
| 673 | 16 | throw new \RuntimeException('Invalid stack operation OP_3DUP'); |
|||||
| 674 | } |
||||||
| 675 | 28 | $string1 = $mainStack[-3]; |
|||||
| 676 | 28 | $string2 = $mainStack[-2]; |
|||||
| 677 | 28 | $string3 = $mainStack[-1]; |
|||||
| 678 | 28 | $mainStack->push($string1); |
|||||
| 679 | 28 | $mainStack->push($string2); |
|||||
| 680 | 28 | $mainStack->push($string3); |
|||||
| 681 | 28 | break; |
|||||
| 682 | |||||||
| 683 | case Opcodes::OP_2OVER: |
||||||
| 684 | 20 | if (count($mainStack) < 4) { |
|||||
| 685 | 12 | throw new \RuntimeException('Invalid stack operation OP_2OVER'); |
|||||
| 686 | } |
||||||
| 687 | 8 | $string1 = $mainStack[-4]; |
|||||
| 688 | 8 | $string2 = $mainStack[-3]; |
|||||
| 689 | 8 | $mainStack->push($string1); |
|||||
| 690 | 8 | $mainStack->push($string2); |
|||||
| 691 | 8 | break; |
|||||
| 692 | |||||||
| 693 | case Opcodes::OP_2ROT: |
||||||
| 694 | 40 | if (count($mainStack) < 6) { |
|||||
| 695 | 4 | throw new \RuntimeException('Invalid stack operation OP_2ROT'); |
|||||
| 696 | } |
||||||
| 697 | 36 | $string1 = $mainStack[-6]; |
|||||
| 698 | 36 | $string2 = $mainStack[-5]; |
|||||
| 699 | 36 | unset($mainStack[-6], $mainStack[-5]); |
|||||
| 700 | 36 | $mainStack->push($string1); |
|||||
| 701 | 36 | $mainStack->push($string2); |
|||||
| 702 | 36 | break; |
|||||
| 703 | |||||||
| 704 | case Opcodes::OP_2SWAP: |
||||||
| 705 | 20 | if (count($mainStack) < 4) { |
|||||
| 706 | 12 | throw new \RuntimeException('Invalid stack operation OP_2SWAP'); |
|||||
| 707 | } |
||||||
| 708 | 8 | $mainStack->swap(-3, -1); |
|||||
| 709 | 8 | $mainStack->swap(-4, -2); |
|||||
| 710 | 8 | break; |
|||||
| 711 | |||||||
| 712 | case Opcodes::OP_SIZE: |
||||||
| 713 | 124 | if ($mainStack->isEmpty()) { |
|||||
| 714 | 8 | throw new \RuntimeException('Invalid stack operation OP_SIZE'); |
|||||
| 715 | } |
||||||
| 716 | 116 | $size = Number::int($mainStack[-1]->getSize()); |
|||||
| 717 | 116 | $mainStack->push($size->getBuffer()); |
|||||
| 718 | 116 | break; |
|||||
| 719 | |||||||
| 720 | case Opcodes::OP_EQUAL: |
||||||
| 721 | case Opcodes::OP_EQUALVERIFY: |
||||||
| 722 | 1258 | if (count($mainStack) < 2) { |
|||||
| 723 | 16 | throw new \RuntimeException('Invalid stack operation OP_EQUAL'); |
|||||
| 724 | } |
||||||
| 725 | |||||||
| 726 | 1242 | $equal = $mainStack[-2]->equals($mainStack[-1]); |
|||||
| 727 | 1242 | $mainStack->pop(); |
|||||
| 728 | 1242 | $mainStack->pop(); |
|||||
| 729 | 1242 | $mainStack->push($equal ? $this->vchTrue : $this->vchFalse); |
|||||
| 730 | 1242 | if ($opCode === Opcodes::OP_EQUALVERIFY) { |
|||||
| 731 | 180 | if ($equal) { |
|||||
| 732 | 144 | $mainStack->pop(); |
|||||
| 733 | } else { |
||||||
| 734 | 36 | throw new \RuntimeException('Error EQUALVERIFY'); |
|||||
| 735 | } |
||||||
| 736 | } |
||||||
| 737 | |||||||
| 738 | 1210 | break; |
|||||
| 739 | |||||||
| 740 | // Arithmetic operations |
||||||
| 741 | case $opCode >= Opcodes::OP_1ADD && $opCode <= Opcodes::OP_0NOTEQUAL: |
||||||
| 742 | 444 | if ($mainStack->isEmpty()) { |
|||||
| 743 | 24 | throw new \Exception('Invalid stack operation 1ADD-OP_0NOTEQUAL'); |
|||||
| 744 | } |
||||||
| 745 | |||||||
| 746 | 420 | $num = Number::buffer($mainStack[-1], $minimal)->getGmp(); |
|||||
| 747 | |||||||
| 748 | 328 | if ($opCode === Opcodes::OP_1ADD) { |
|||||
| 749 | 28 | $num = $this->math->add($num, gmp_init(1)); |
|||||
|
0 ignored issues
–
show
It seems like
gmp_init(1) can also be of type resource; however, parameter $addend of Mdanter\Ecc\Math\GmpMath::add() does only seem to accept GMP, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 750 | 300 | } elseif ($opCode === Opcodes::OP_1SUB) { |
|||||
| 751 | 12 | $num = $this->math->sub($num, gmp_init(1)); |
|||||
| 752 | 288 | } elseif ($opCode === Opcodes::OP_2MUL) { |
|||||
| 753 | $num = $this->math->mul(gmp_init(2), $num); |
||||||
|
0 ignored issues
–
show
It seems like
gmp_init(2) can also be of type resource; however, parameter $multiplier of Mdanter\Ecc\Math\GmpMath::mul() does only seem to accept GMP, maybe add an additional type check?
(
Ignorable by Annotation
)
If this is a false-positive, you can also ignore this issue in your code via the
Loading history...
|
|||||||
| 754 | 288 | } elseif ($opCode === Opcodes::OP_NEGATE) { |
|||||
| 755 | 16 | $num = $this->math->sub($zero, $num); |
|||||
| 756 | 276 | } elseif ($opCode === Opcodes::OP_ABS) { |
|||||
| 757 | 20 | if ($this->math->cmp($num, $zero) < 0) { |
|||||
| 758 | 20 | $num = $this->math->sub($zero, $num); |
|||||
| 759 | } |
||||||
| 760 | 256 | } elseif ($opCode === Opcodes::OP_NOT) { |
|||||
| 761 | 232 | $num = gmp_init($this->math->cmp($num, $zero) === 0 ? 1 : 0); |
|||||
| 762 | } else { |
||||||
| 763 | // is OP_0NOTEQUAL |
||||||
| 764 | 24 | $num = gmp_init($this->math->cmp($num, $zero) !== 0 ? 1 : 0); |
|||||
| 765 | } |
||||||
| 766 | |||||||
| 767 | 328 | $mainStack->pop(); |
|||||
| 768 | |||||||
| 769 | 328 | $buffer = Number::int(gmp_strval($num, 10))->getBuffer(); |
|||||
| 770 | |||||||
| 771 | 328 | $mainStack->push($buffer); |
|||||
| 772 | 328 | break; |
|||||
| 773 | |||||||
| 774 | 2209 | case $opCode >= Opcodes::OP_ADD && $opCode <= Opcodes::OP_MAX: |
|||||
| 775 | 656 | if (count($mainStack) < 2) { |
|||||
| 776 | 52 | throw new \Exception('Invalid stack operation (OP_ADD - OP_MAX)'); |
|||||
| 777 | } |
||||||
| 778 | |||||||
| 779 | 604 | $num1 = Number::buffer($mainStack[-2], $minimal)->getGmp(); |
|||||
| 780 | 536 | $num2 = Number::buffer($mainStack[-1], $minimal)->getGmp(); |
|||||
| 781 | |||||||
| 782 | 484 | if ($opCode === Opcodes::OP_ADD) { |
|||||
| 783 | 120 | $num = $this->math->add($num1, $num2); |
|||||
| 784 | 388 | } else if ($opCode === Opcodes::OP_SUB) { |
|||||
| 785 | 24 | $num = $this->math->sub($num1, $num2); |
|||||
| 786 | 364 | } else if ($opCode === Opcodes::OP_BOOLAND) { |
|||||
| 787 | 32 | $num = (int) ($this->math->cmp($num1, $zero) !== 0 && $this->math->cmp($num2, $zero) !== 0); |
|||||
| 788 | 332 | } else if ($opCode === Opcodes::OP_BOOLOR) { |
|||||
| 789 | 32 | $num = (int) ($this->math->cmp($num1, $zero) !== 0 || $this->math->cmp($num2, $zero) !== 0); |
|||||
| 790 | 300 | } elseif ($opCode === Opcodes::OP_NUMEQUAL) { |
|||||
| 791 | 112 | $num = (int) ($this->math->cmp($num1, $num2) === 0); |
|||||
| 792 | 220 | } elseif ($opCode === Opcodes::OP_NUMEQUALVERIFY) { |
|||||
| 793 | 16 | $num = (int) ($this->math->cmp($num1, $num2) === 0); |
|||||
| 794 | 204 | } elseif ($opCode === Opcodes::OP_NUMNOTEQUAL) { |
|||||
| 795 | 20 | $num = (int) ($this->math->cmp($num1, $num2) !== 0); |
|||||
| 796 | 184 | } elseif ($opCode === Opcodes::OP_LESSTHAN) { |
|||||
| 797 | 32 | $num = (int) ($this->math->cmp($num1, $num2) < 0); |
|||||
| 798 | 152 | } elseif ($opCode === Opcodes::OP_GREATERTHAN) { |
|||||
| 799 | 32 | $num = (int) ($this->math->cmp($num1, $num2) > 0); |
|||||
| 800 | 120 | } elseif ($opCode === Opcodes::OP_LESSTHANOREQUAL) { |
|||||
| 801 | 32 | $num = (int) ($this->math->cmp($num1, $num2) <= 0); |
|||||
| 802 | 88 | } elseif ($opCode === Opcodes::OP_GREATERTHANOREQUAL) { |
|||||
| 803 | 32 | $num = (int) ($this->math->cmp($num1, $num2) >= 0); |
|||||
| 804 | 56 | } elseif ($opCode === Opcodes::OP_MIN) { |
|||||
| 805 | 28 | $num = ($this->math->cmp($num1, $num2) <= 0) ? $num1 : $num2; |
|||||
| 806 | } else { |
||||||
| 807 | 28 | $num = ($this->math->cmp($num1, $num2) >= 0) ? $num1 : $num2; |
|||||
| 808 | } |
||||||
| 809 | |||||||
| 810 | 484 | $mainStack->pop(); |
|||||
| 811 | 484 | $mainStack->pop(); |
|||||
| 812 | 484 | $buffer = Number::int(gmp_strval($num, 10))->getBuffer(); |
|||||
| 813 | 484 | $mainStack->push($buffer); |
|||||
| 814 | |||||||
| 815 | 484 | if ($opCode === Opcodes::OP_NUMEQUALVERIFY) { |
|||||
| 816 | 16 | if ($this->castToBool($mainStack[-1])) { |
|||||
| 817 | 16 | $mainStack->pop(); |
|||||
| 818 | } else { |
||||||
| 819 | throw new \RuntimeException('NUM EQUAL VERIFY error'); |
||||||
| 820 | } |
||||||
| 821 | } |
||||||
| 822 | 484 | break; |
|||||
| 823 | |||||||
| 824 | 1553 | case Opcodes::OP_WITHIN: |
|||||
| 825 | 60 | if (count($mainStack) < 3) { |
|||||
| 826 | 4 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 827 | } |
||||||
| 828 | |||||||
| 829 | 56 | $num1 = Number::buffer($mainStack[-3], $minimal)->getGmp(); |
|||||
| 830 | 52 | $num2 = Number::buffer($mainStack[-2], $minimal)->getGmp(); |
|||||
| 831 | 48 | $num3 = Number::buffer($mainStack[-1], $minimal)->getGmp(); |
|||||
| 832 | |||||||
| 833 | 44 | $value = $this->math->cmp($num2, $num1) <= 0 && $this->math->cmp($num1, $num3) < 0; |
|||||
| 834 | 44 | $mainStack->pop(); |
|||||
| 835 | 44 | $mainStack->pop(); |
|||||
| 836 | 44 | $mainStack->pop(); |
|||||
| 837 | 44 | $mainStack->push($value ? $this->vchTrue : $this->vchFalse); |
|||||
| 838 | 44 | break; |
|||||
| 839 | |||||||
| 840 | // Hash operation |
||||||
| 841 | 1493 | case Opcodes::OP_RIPEMD160: |
|||||
| 842 | 1469 | case Opcodes::OP_SHA1: |
|||||
| 843 | 1437 | case Opcodes::OP_SHA256: |
|||||
| 844 | 1413 | case Opcodes::OP_HASH160: |
|||||
| 845 | 1183 | case Opcodes::OP_HASH256: |
|||||
| 846 | 415 | if ($mainStack->isEmpty()) { |
|||||
| 847 | 41 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 848 | } |
||||||
| 849 | |||||||
| 850 | 374 | $buffer = $mainStack[-1]; |
|||||
| 851 | 374 | if ($opCode === Opcodes::OP_RIPEMD160) { |
|||||
| 852 | 20 | $hash = Hash::ripemd160($buffer); |
|||||
| 853 | 358 | } elseif ($opCode === Opcodes::OP_SHA1) { |
|||||
| 854 | 24 | $hash = Hash::sha1($buffer); |
|||||
| 855 | 334 | } elseif ($opCode === Opcodes::OP_SHA256) { |
|||||
| 856 | 24 | $hash = Hash::sha256($buffer); |
|||||
| 857 | 318 | } elseif ($opCode === Opcodes::OP_HASH160) { |
|||||
| 858 | 298 | $hash = Hash::sha256ripe160($buffer); |
|||||
| 859 | } else { |
||||||
| 860 | 20 | $hash = Hash::sha256d($buffer); |
|||||
| 861 | } |
||||||
| 862 | |||||||
| 863 | 374 | $mainStack->pop(); |
|||||
| 864 | 374 | $mainStack->push($hash); |
|||||
| 865 | 374 | break; |
|||||
| 866 | |||||||
| 867 | 1155 | case Opcodes::OP_CODESEPARATOR: |
|||||
| 868 | 4 | $hashStartPos = $parser->getPosition(); |
|||||
| 869 | 4 | break; |
|||||
| 870 | |||||||
| 871 | 1151 | case Opcodes::OP_CHECKSIG: |
|||||
| 872 | 815 | case Opcodes::OP_CHECKSIGVERIFY: |
|||||
| 873 | 336 | if (count($mainStack) < 2) { |
|||||
| 874 | 8 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 875 | } |
||||||
| 876 | |||||||
| 877 | 328 | $vchPubKey = $mainStack[-1]; |
|||||
| 878 | 328 | $vchSig = $mainStack[-2]; |
|||||
| 879 | |||||||
| 880 | 328 | $scriptCode = new Script($script->getBuffer()->slice($hashStartPos)); |
|||||
| 881 | |||||||
| 882 | 328 | $success = $checker->checkSig($scriptCode, $vchSig, $vchPubKey, $sigVersion, $flags); |
|||||
| 883 | |||||||
| 884 | 232 | $mainStack->pop(); |
|||||
| 885 | 232 | $mainStack->pop(); |
|||||
| 886 | 232 | $mainStack->push($success ? $this->vchTrue : $this->vchFalse); |
|||||
| 887 | |||||||
| 888 | 232 | if (!$success && ($flags & self::VERIFY_NULLFAIL) && $vchSig->getSize() > 0) { |
|||||
| 889 | 4 | throw new ScriptRuntimeException(self::VERIFY_NULLFAIL, 'Signature must be zero for failed OP_CHECK(MULTIS)SIG operation'); |
|||||
| 890 | } |
||||||
| 891 | |||||||
| 892 | 228 | if ($opCode === Opcodes::OP_CHECKSIGVERIFY) { |
|||||
| 893 | if ($success) { |
||||||
| 894 | $mainStack->pop(); |
||||||
| 895 | } else { |
||||||
| 896 | throw new \RuntimeException('Checksig verify'); |
||||||
| 897 | } |
||||||
| 898 | } |
||||||
| 899 | 228 | break; |
|||||
| 900 | |||||||
| 901 | 815 | case Opcodes::OP_CHECKMULTISIG: |
|||||
| 902 | 495 | case Opcodes::OP_CHECKMULTISIGVERIFY: |
|||||
| 903 | 448 | $i = 1; |
|||||
| 904 | 448 | if (count($mainStack) < $i) { |
|||||
| 905 | 4 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 906 | } |
||||||
| 907 | |||||||
| 908 | 444 | $keyCount = Number::buffer($mainStack[-$i], $minimal)->getInt(); |
|||||
| 909 | 436 | if ($keyCount < 0 || $keyCount > 20) { |
|||||
| 910 | 8 | throw new \RuntimeException('OP_CHECKMULTISIG: Public key count exceeds 20'); |
|||||
| 911 | } |
||||||
| 912 | |||||||
| 913 | 428 | $opCount += $keyCount; |
|||||
| 914 | 428 | $this->checkOpcodeCount($opCount); |
|||||
| 915 | |||||||
| 916 | // Extract positions of the keys, and signatures, from the stack. |
||||||
| 917 | 428 | $ikey = ++$i; |
|||||
| 918 | 428 | $ikey2 = $keyCount + 2; |
|||||
| 919 | 428 | $i += $keyCount; |
|||||
| 920 | 428 | if (count($mainStack) < $i) { |
|||||
| 921 | 4 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 922 | } |
||||||
| 923 | |||||||
| 924 | 424 | $sigCount = Number::buffer($mainStack[-$i], $minimal)->getInt(); |
|||||
| 925 | 412 | if ($sigCount < 0 || $sigCount > $keyCount) { |
|||||
| 926 | 8 | throw new \RuntimeException('Invalid Signature count'); |
|||||
| 927 | } |
||||||
| 928 | |||||||
| 929 | 404 | $isig = ++$i; |
|||||
| 930 | 404 | $i += $sigCount; |
|||||
| 931 | |||||||
| 932 | // Extract the script since the last OP_CODESEPARATOR |
||||||
| 933 | 404 | $scriptCode = new Script($script->getBuffer()->slice($hashStartPos)); |
|||||
| 934 | |||||||
| 935 | 404 | $fSuccess = true; |
|||||
| 936 | 404 | while ($fSuccess && $sigCount > 0) { |
|||||
| 937 | // Fetch the signature and public key |
||||||
| 938 | 164 | $sig = $mainStack[-$isig]; |
|||||
| 939 | 160 | $pubkey = $mainStack[-$ikey]; |
|||||
| 940 | |||||||
| 941 | 160 | if ($checker->checkSig($scriptCode, $sig, $pubkey, $sigVersion, $flags)) { |
|||||
| 942 | 56 | $isig++; |
|||||
| 943 | 56 | $sigCount--; |
|||||
| 944 | } |
||||||
| 945 | |||||||
| 946 | 136 | $ikey++; |
|||||
| 947 | 136 | $keyCount--; |
|||||
| 948 | |||||||
| 949 | // If there are more signatures left than keys left, |
||||||
| 950 | // then too many signatures have failed. Exit early, |
||||||
| 951 | // without checking any further signatures. |
||||||
| 952 | 136 | if ($sigCount > $keyCount) { |
|||||
| 953 | 88 | $fSuccess = false; |
|||||
| 954 | } |
||||||
| 955 | } |
||||||
| 956 | |||||||
| 957 | 368 | while ($i-- > 1) { |
|||||
| 958 | // If the operation failed, we require that all signatures must be empty vector |
||||||
| 959 | 368 | if (!$fSuccess && ($flags & self::VERIFY_NULLFAIL) && !$ikey2 && $mainStack[-1]->getSize() > 0) { |
|||||
| 960 | 8 | throw new ScriptRuntimeException(self::VERIFY_NULLFAIL, 'Bad signature must be empty vector'); |
|||||
| 961 | } |
||||||
| 962 | |||||||
| 963 | 368 | if ($ikey2 > 0) { |
|||||
| 964 | 368 | $ikey2--; |
|||||
| 965 | } |
||||||
| 966 | |||||||
| 967 | 368 | $mainStack->pop(); |
|||||
| 968 | } |
||||||
| 969 | |||||||
| 970 | // A bug causes CHECKMULTISIG to consume one extra argument |
||||||
| 971 | // whose contents were not checked in any way. |
||||||
| 972 | // |
||||||
| 973 | // Unfortunately this is a potential source of mutability, |
||||||
| 974 | // so optionally verify it is exactly equal to zero prior |
||||||
| 975 | // to removing it from the stack. |
||||||
| 976 | 360 | if ($mainStack->isEmpty()) { |
|||||
| 977 | 4 | throw new \RuntimeException('Invalid stack operation'); |
|||||
| 978 | } |
||||||
| 979 | |||||||
| 980 | 360 | if ($flags & self::VERIFY_NULL_DUMMY && $mainStack[-1]->getSize() !== 0) { |
|||||
| 981 | 12 | throw new ScriptRuntimeException(self::VERIFY_NULL_DUMMY, 'Extra P2SH stack value should be OP_0'); |
|||||
| 982 | } |
||||||
| 983 | |||||||
| 984 | 348 | $mainStack->pop(); |
|||||
| 985 | 348 | $mainStack->push($fSuccess ? $this->vchTrue : $this->vchFalse); |
|||||
| 986 | |||||||
| 987 | 348 | if ($opCode === Opcodes::OP_CHECKMULTISIGVERIFY) { |
|||||
| 988 | 120 | if ($fSuccess) { |
|||||
| 989 | 120 | $mainStack->pop(); |
|||||
| 990 | } else { |
||||||
| 991 | throw new \RuntimeException('OP_CHECKMULTISIG verify'); |
||||||
| 992 | } |
||||||
| 993 | } |
||||||
| 994 | 348 | break; |
|||||
| 995 | |||||||
| 996 | default: |
||||||
| 997 | 367 | throw new \RuntimeException('Opcode not found'); |
|||||
| 998 | } |
||||||
| 999 | |||||||
| 1000 | 4066 | if (count($mainStack) + count($altStack) > 1000) { |
|||||
| 1001 | 8 | throw new \RuntimeException('Invalid stack size, exceeds 1000'); |
|||||
| 1002 | } |
||||||
| 1003 | } |
||||||
| 1004 | } |
||||||
| 1005 | |||||||
| 1006 | 4459 | if (count($vfStack) !== 0) { |
|||||
| 1007 | 24 | throw new \RuntimeException('Unbalanced conditional at script end'); |
|||||
| 1008 | } |
||||||
| 1009 | |||||||
| 1010 | 4439 | return true; |
|||||
| 1011 | 1665 | } catch (ScriptRuntimeException $e) { |
|||||
| 1012 | // echo "\n Runtime: " . $e->getMessage() . "\n" . $e->getTraceAsString() . PHP_EOL; |
||||||
| 1013 | // Failure due to script tags, can access flag: $e->getFailureFlag() |
||||||
| 1014 | 344 | return false; |
|||||
| 1015 | 1321 | } catch (\Exception $e) { |
|||||
| 1016 | // echo "\n General: " . $e->getMessage() . PHP_EOL . $e->getTraceAsString() . PHP_EOL; |
||||||
| 1017 | 1321 | return false; |
|||||
| 1018 | } |
||||||
| 1019 | } |
||||||
| 1020 | } |
||||||
| 1021 |