/** * @param ScriptInterface $script */ public function __construct(ScriptInterface $script) { $publicKeys = []; $parse = $script->getScriptParser()->decode(); if (count($parse) < 4) { throw new \InvalidArgumentException('Malformed multisig script'); } $mCode = $parse[0]->getOp(); $nCode = $parse[count($parse) - 2]->getOp(); $this->m = (int) \BitWasp\Bitcoin\Script\decodeOpN($mCode); foreach (array_slice($parse, 1, -2) as $key) { /** @var \BitWasp\Bitcoin\Script\Parser\Operation $key */ if (!$key->isPush()) { throw new \RuntimeException('Malformed multisig script'); } $publicKeys[] = PublicKeyFactory::fromHex($key->getData()); } $n = \BitWasp\Bitcoin\Script\decodeOpN($nCode); $this->n = count($publicKeys); if ($this->n === 0 || $this->n !== $n) { throw new \LogicException('No public keys found in script'); } $this->script = $script; $this->keys = $publicKeys; }
/** * @param ScriptInterface $script * @param Stack $mainStack * @param int $sigVersion * @param int $flags * @param Checker $checker * @return bool */ public function evaluate(ScriptInterface $script, Stack $mainStack, $sigVersion, $flags, Checker $checker) { $math = $this->math; $hashStartPos = 0; $this->opCount = 0; $altStack = new Stack(); $vfStack = new Stack(); $minimal = ($flags & self::VERIFY_MINIMALDATA) != 0; $parser = $script->getScriptParser(); if ($script->getBuffer()->getSize() > 10000) { return false; } try { foreach ($parser as $operation) { $opCode = $operation->getOp(); $pushData = $operation->getData(); $fExec = $this->checkExec($vfStack); // If pushdata was written to if ($operation->isPush() && $operation->getDataSize() > InterpreterInterface::MAX_SCRIPT_ELEMENT_SIZE) { throw new \RuntimeException('Error - push size'); } // OP_RESERVED should not count towards opCount if ($opCode > Opcodes::OP_16 && ++$this->opCount) { $this->checkOpcodeCount(); } if (in_array($opCode, $this->disabledOps, true)) { throw new \RuntimeException('Disabled Opcode'); } if ($fExec && $operation->isPush()) { // In range of a pushdata opcode if ($minimal && !$this->checkMinimalPush($opCode, $pushData)) { throw new ScriptRuntimeException(self::VERIFY_MINIMALDATA, 'Minimal pushdata required'); } $mainStack->push($pushData); // echo " - [pushed '" . $pushData->getHex() . "']\n"; } elseif ($fExec || $opCode !== Opcodes::OP_IF && $opCode !== Opcodes::OP_ENDIF) { // echo "OPCODE - " . $script->getOpcodes()->getOp($opCode) . "\n"; switch ($opCode) { case Opcodes::OP_1NEGATE: case Opcodes::OP_1: case Opcodes::OP_2: case Opcodes::OP_3: case Opcodes::OP_4: case Opcodes::OP_5: case Opcodes::OP_6: case Opcodes::OP_7: case Opcodes::OP_8: case Opcodes::OP_9: case Opcodes::OP_10: case Opcodes::OP_11: case Opcodes::OP_12: case Opcodes::OP_13: case Opcodes::OP_14: case Opcodes::OP_15: case Opcodes::OP_16: $num = \BitWasp\Bitcoin\Script\decodeOpN($opCode); $mainStack->push(Number::int($num)->getBuffer()); break; case Opcodes::OP_CHECKLOCKTIMEVERIFY: if (!$flags & self::VERIFY_CHECKLOCKTIMEVERIFY) { if ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); } break; } if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation - CLTV'); } $lockTime = Number::buffer($mainStack[-1], $minimal, 5, $math); if (!$checker->checkLockTime($lockTime)) { throw new ScriptRuntimeException(self::VERIFY_CHECKLOCKTIMEVERIFY, 'Unsatisfied locktime'); } break; case Opcodes::OP_CHECKSEQUENCEVERIFY: if (!$flags & self::VERIFY_CHECKSEQUENCEVERIFY) { if ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); } break; } if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation - CSV'); } $sequence = Number::buffer($mainStack[-1], $minimal, 5, $math); $nSequence = $sequence->getInt(); if ($math->cmp($nSequence, 0) < 0) { throw new ScriptRuntimeException(self::VERIFY_CHECKSEQUENCEVERIFY, 'Negative locktime'); } if ($math->cmp($math->bitwiseAnd($nSequence, TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG), '0') !== 0) { break; } if (!$checker->checkSequence($sequence)) { throw new ScriptRuntimeException(self::VERIFY_CHECKSEQUENCEVERIFY, 'Unsatisfied locktime'); } break; case Opcodes::OP_NOP1: case Opcodes::OP_NOP4: case Opcodes::OP_NOP5: case Opcodes::OP_NOP6: case Opcodes::OP_NOP7: case Opcodes::OP_NOP8: case Opcodes::OP_NOP9: case Opcodes::OP_NOP10: if ($flags & self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS) { throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); } break; case Opcodes::OP_NOP: break; case Opcodes::OP_IF: case Opcodes::OP_NOTIF: // <expression> if [statements] [else [statements]] endif $value = false; if ($fExec) { if ($mainStack->isEmpty()) { throw new \RuntimeException('Unbalanced conditional'); } $buffer = Number::buffer($mainStack->pop(), $minimal)->getBuffer(); $value = $this->castToBool($buffer); if ($opCode === Opcodes::OP_NOTIF) { $value = !$value; } } $vfStack->push($value ? $this->vchTrue : $this->vchFalse); break; case Opcodes::OP_ELSE: if ($vfStack->isEmpty()) { throw new \RuntimeException('Unbalanced conditional'); } $vfStack->push(!$vfStack->end() ? $this->vchTrue : $this->vchFalse); break; case Opcodes::OP_ENDIF: if ($vfStack->isEmpty()) { throw new \RuntimeException('Unbalanced conditional'); } break; case Opcodes::OP_VERIFY: if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation'); } $value = $this->castToBool($mainStack[-1]); if (!$value) { throw new \RuntimeException('Error: verify'); } $mainStack->pop(); break; case Opcodes::OP_TOALTSTACK: if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_TOALTSTACK'); } $altStack->push($mainStack->pop()); break; case Opcodes::OP_FROMALTSTACK: if ($altStack->isEmpty()) { throw new \RuntimeException('Invalid alt-stack operation OP_FROMALTSTACK'); } $mainStack->push($altStack->pop()); break; case Opcodes::OP_IFDUP: // If top value not zero, duplicate it. if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_IFDUP'); } $vch = $mainStack[-1]; if ($this->castToBool($vch)) { $mainStack->push($vch); } break; case Opcodes::OP_DEPTH: $num = count($mainStack); $depth = Number::int($num)->getBuffer(); $mainStack->push($depth); break; case Opcodes::OP_DROP: if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_DROP'); } $mainStack->pop(); break; case Opcodes::OP_DUP: if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_DUP'); } $vch = $mainStack[-1]; $mainStack->push($vch); break; case Opcodes::OP_NIP: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_NIP'); } unset($mainStack[-2]); break; case Opcodes::OP_OVER: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_OVER'); } $vch = $mainStack[-2]; $mainStack->push($vch); break; case Opcodes::OP_ROT: if (count($mainStack) < 3) { throw new \RuntimeException('Invalid stack operation OP_ROT'); } $mainStack->swap(-3, -2); $mainStack->swap(-2, -1); break; case Opcodes::OP_SWAP: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_SWAP'); } $mainStack->swap(-2, -1); break; case Opcodes::OP_TUCK: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_TUCK'); } $vch = $mainStack[-1]; $mainStack->add(count($mainStack) - 1 - 2, $vch); break; case Opcodes::OP_PICK: case Opcodes::OP_ROLL: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_PICK'); } $n = Number::buffer($mainStack[-1], $minimal, 4)->getInt(); $mainStack->pop(); if ($math->cmp($n, 0) < 0 || $math->cmp($n, count($mainStack)) >= 0) { throw new \RuntimeException('Invalid stack operation OP_PICK'); } $pos = (int) $math->sub($math->sub(0, $n), 1); $vch = $mainStack[$pos]; if ($opCode === Opcodes::OP_ROLL) { unset($mainStack[$pos]); } $mainStack->push($vch); break; case Opcodes::OP_2DROP: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_2DROP'); } $mainStack->pop(); $mainStack->pop(); break; case Opcodes::OP_2DUP: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_2DUP'); } $string1 = $mainStack[-2]; $string2 = $mainStack[-1]; $mainStack->push($string1); $mainStack->push($string2); break; case Opcodes::OP_3DUP: if (count($mainStack) < 3) { throw new \RuntimeException('Invalid stack operation OP_3DUP'); } $string1 = $mainStack[-3]; $string2 = $mainStack[-2]; $string3 = $mainStack[-1]; $mainStack->push($string1); $mainStack->push($string2); $mainStack->push($string3); break; case Opcodes::OP_2OVER: if (count($mainStack) < 4) { throw new \RuntimeException('Invalid stack operation OP_2OVER'); } $string1 = $mainStack[-4]; $string2 = $mainStack[-3]; $mainStack->push($string1); $mainStack->push($string2); break; case Opcodes::OP_2ROT: if (count($mainStack) < 6) { throw new \RuntimeException('Invalid stack operation OP_2ROT'); } $string1 = $mainStack[-6]; $string2 = $mainStack[-5]; unset($mainStack[-6], $mainStack[-5]); $mainStack->push($string1); $mainStack->push($string2); break; case Opcodes::OP_2SWAP: if (count($mainStack) < 4) { throw new \RuntimeException('Invalid stack operation OP_2SWAP'); } $mainStack->swap(-3, -1); $mainStack->swap(-4, -2); break; case Opcodes::OP_SIZE: if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_SIZE'); } $size = Number::int($mainStack[-1]->getSize()); $mainStack->push($size->getBuffer()); break; case Opcodes::OP_EQUAL: case Opcodes::OP_EQUALVERIFY: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_EQUAL'); } $equal = $mainStack[-2]->equals($mainStack[-1]); $mainStack->pop(); $mainStack->pop(); $mainStack->push($equal ? $this->vchTrue : $this->vchFalse); if ($opCode === Opcodes::OP_EQUALVERIFY) { if ($equal) { $mainStack->pop(); } else { throw new \RuntimeException('Error EQUALVERIFY'); } } break; // Arithmetic operations // Arithmetic operations case $opCode >= Opcodes::OP_1ADD && $opCode <= Opcodes::OP_0NOTEQUAL: if ($mainStack->isEmpty()) { throw new \Exception('Invalid stack operation 1ADD-OP_0NOTEQUAL'); } $num = Number::buffer($mainStack[-1], $minimal)->getInt(); if ($opCode === Opcodes::OP_1ADD) { $num = $math->add($num, '1'); } elseif ($opCode === Opcodes::OP_1SUB) { $num = $math->sub($num, '1'); } elseif ($opCode === Opcodes::OP_2MUL) { $num = $math->mul(2, $num); } elseif ($opCode === Opcodes::OP_NEGATE) { $num = $math->sub(0, $num); } elseif ($opCode === Opcodes::OP_ABS) { if ($math->cmp($num, '0') < 0) { $num = $math->sub(0, $num); } } elseif ($opCode === Opcodes::OP_NOT) { $num = (int) $math->cmp($num, '0') === 0; } else { // is OP_0NOTEQUAL $num = (int) ($math->cmp($num, '0') !== 0); } $mainStack->pop(); $buffer = Number::int($num)->getBuffer(); $mainStack->push($buffer); break; case $opCode >= Opcodes::OP_ADD && $opCode <= Opcodes::OP_MAX: if (count($mainStack) < 2) { throw new \Exception('Invalid stack operation (OP_ADD - OP_MAX)'); } $num1 = Number::buffer($mainStack[-2], $minimal)->getInt(); $num2 = Number::buffer($mainStack[-1], $minimal)->getInt(); if ($opCode === Opcodes::OP_ADD) { $num = $math->add($num1, $num2); } else { if ($opCode === Opcodes::OP_SUB) { $num = $math->sub($num1, $num2); } else { if ($opCode === Opcodes::OP_BOOLAND) { $num = $math->cmp($num1, '0') !== 0 && $math->cmp($num2, '0') !== 0; } else { if ($opCode === Opcodes::OP_BOOLOR) { $num = $math->cmp($num1, '0') !== 0 || $math->cmp($num2, '0') !== 0; } elseif ($opCode === Opcodes::OP_NUMEQUAL) { $num = $math->cmp($num1, $num2) === 0; } elseif ($opCode === Opcodes::OP_NUMEQUALVERIFY) { $num = $math->cmp($num1, $num2) === 0; } elseif ($opCode === Opcodes::OP_NUMNOTEQUAL) { $num = $math->cmp($num1, $num2) !== 0; } elseif ($opCode === Opcodes::OP_LESSTHAN) { $num = $math->cmp($num1, $num2) < 0; } elseif ($opCode === Opcodes::OP_GREATERTHAN) { $num = $math->cmp($num1, $num2) > 0; } elseif ($opCode === Opcodes::OP_LESSTHANOREQUAL) { $num = $math->cmp($num1, $num2) <= 0; } elseif ($opCode === Opcodes::OP_GREATERTHANOREQUAL) { $num = $math->cmp($num1, $num2) >= 0; } elseif ($opCode === Opcodes::OP_MIN) { $num = $math->cmp($num1, $num2) <= 0 ? $num1 : $num2; } else { $num = $math->cmp($num1, $num2) >= 0 ? $num1 : $num2; } } } } $mainStack->pop(); $mainStack->pop(); $buffer = Number::int($num)->getBuffer(); $mainStack->push($buffer); if ($opCode === Opcodes::OP_NUMEQUALVERIFY) { if ($this->castToBool($mainStack[-1])) { $mainStack->pop(); } else { throw new \RuntimeException('NUM EQUAL VERIFY error'); } } break; case Opcodes::OP_WITHIN: if (count($mainStack) < 3) { throw new \RuntimeException('Invalid stack operation'); } $num1 = Number::buffer($mainStack[-3], $minimal)->getInt(); $num2 = Number::buffer($mainStack[-2], $minimal)->getInt(); $num3 = Number::buffer($mainStack[-1], $minimal)->getInt(); $value = $math->cmp($num2, $num1) <= 0 && $math->cmp($num1, $num3) < 0; $mainStack->pop(); $mainStack->pop(); $mainStack->pop(); $mainStack->push($value ? $this->vchTrue : $this->vchFalse); break; // Hash operation // Hash operation case Opcodes::OP_RIPEMD160: case Opcodes::OP_SHA1: case Opcodes::OP_SHA256: case Opcodes::OP_HASH160: case Opcodes::OP_HASH256: if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation'); } $buffer = $mainStack[-1]; if ($opCode === Opcodes::OP_RIPEMD160) { $hash = Hash::ripemd160($buffer); } elseif ($opCode === Opcodes::OP_SHA1) { $hash = Hash::sha1($buffer); } elseif ($opCode === Opcodes::OP_SHA256) { $hash = Hash::sha256($buffer); } elseif ($opCode === Opcodes::OP_HASH160) { $hash = Hash::sha256ripe160($buffer); } else { $hash = Hash::sha256d($buffer); } $mainStack->pop(); $mainStack->push($hash); break; case Opcodes::OP_CODESEPARATOR: $hashStartPos = $parser->getPosition(); break; case Opcodes::OP_CHECKSIG: case Opcodes::OP_CHECKSIGVERIFY: if (count($mainStack) < 2) { throw new \RuntimeException('Invalid stack operation'); } $vchPubKey = $mainStack[-1]; $vchSig = $mainStack[-2]; $scriptCode = new Script($script->getBuffer()->slice($hashStartPos)); $success = $checker->checkSig($scriptCode, $vchSig, $vchPubKey, $sigVersion, $flags); $mainStack->pop(); $mainStack->pop(); $mainStack->push($success ? $this->vchTrue : $this->vchFalse); if ($opCode === Opcodes::OP_CHECKSIGVERIFY) { if ($success) { $mainStack->pop(); } else { throw new \RuntimeException('Checksig verify'); } } break; case Opcodes::OP_CHECKMULTISIG: case Opcodes::OP_CHECKMULTISIGVERIFY: $i = 1; if (count($mainStack) < $i) { throw new \RuntimeException('Invalid stack operation'); } $keyCount = Number::buffer($mainStack[-$i], $minimal)->getInt(); if ($math->cmp($keyCount, 0) < 0 || $math->cmp($keyCount, 20) > 0) { throw new \RuntimeException('OP_CHECKMULTISIG: Public key count exceeds 20'); } $this->opCount += $keyCount; $this->checkOpcodeCount(); // Extract positions of the keys, and signatures, from the stack. $ikey = ++$i; $i += $keyCount; /** @var int $i */ if (count($mainStack) < $i) { throw new \RuntimeException('Invalid stack operation'); } $sigCount = Number::buffer($mainStack[-$i], $minimal)->getInt(); if ($math->cmp($sigCount, 0) < 0 || $math->cmp($sigCount, $keyCount) > 0) { throw new \RuntimeException('Invalid Signature count'); } $isig = ++$i; $i += $sigCount; // Extract the script since the last OP_CODESEPARATOR $scriptCode = new Script($script->getBuffer()->slice($hashStartPos)); $fSuccess = true; while ($fSuccess && $sigCount > 0) { // Fetch the signature and public key $sig = $mainStack[-$isig]; $pubkey = $mainStack[-$ikey]; if ($checker->checkSig($scriptCode, $sig, $pubkey, $sigVersion, $flags)) { $isig++; $sigCount--; } $ikey++; $keyCount--; // If there are more signatures left than keys left, // then too many signatures have failed. Exit early, // without checking any further signatures. if ($sigCount > $keyCount) { $fSuccess = false; } } while ($i-- > 1) { $mainStack->pop(); } // A bug causes CHECKMULTISIG to consume one extra argument // whose contents were not checked in any way. // // Unfortunately this is a potential source of mutability, // so optionally verify it is exactly equal to zero prior // to removing it from the stack. if ($mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation'); } if ($flags & self::VERIFY_NULL_DUMMY && $mainStack[-1]->getSize() !== 0) { throw new ScriptRuntimeException(self::VERIFY_NULL_DUMMY, 'Extra P2SH stack value should be OP_0'); } $mainStack->pop(); $mainStack->push($fSuccess ? $this->vchTrue : $this->vchFalse); if ($opCode === Opcodes::OP_CHECKMULTISIGVERIFY) { if ($fSuccess) { $mainStack->pop(); } else { throw new \RuntimeException('OP_CHECKMULTISIG verify'); } } break; default: throw new \RuntimeException('Opcode not found'); } if (count($mainStack) + count($altStack) > 1000) { throw new \RuntimeException('Invalid stack size, exceeds 1000'); } } } if (!$vfStack->end() === 0) { throw new \RuntimeException('Unbalanced conditional at script end'); } return true; } catch (ScriptRuntimeException $e) { // echo "\n Runtime: " . $e->getMessage() . "\n"; // Failure due to script tags, can access flag: $e->getFailureFlag() return false; } catch (\Exception $e) { // echo "\n General: " . $e->getMessage() . PHP_EOL . $e->getTraceAsString(); return false; } }