/** * @param EcAdapterInterface $ecAdapter * @param ScriptInterface $outputScript * @param RedeemScript $redeemScript */ public function __construct(EcAdapterInterface $ecAdapter, ScriptInterface $outputScript, RedeemScript $redeemScript = null) { $classifier = new OutputClassifier($outputScript); $this->scriptType = $this->prevOutType = $classifier->classify(); // Reclassify if the output is P2SH, so we know how to sign it. if ($this->scriptType == OutputClassifier::PAYTOSCRIPTHASH) { if (null === $redeemScript) { throw new \InvalidArgumentException('Redeem script is required when output is P2SH'); } $rsClassifier = new OutputClassifier($redeemScript); $this->scriptType = $rsClassifier->classify(); } // Gather public keys from redeemScript / outputScript $this->ecAdapter = $ecAdapter; $this->redeemScript = $redeemScript; $this->prevOutScript = $outputScript; // According to scriptType, extract public keys $this->execForInputTypes(function () { // For pay to pub key hash - nothing useful in output script $this->publicKeys = []; }, function () { // For pay to pub key - we can extract this from the output script $chunks = $this->prevOutScript->getScriptParser()->parse(); $this->publicKeys = [PublicKeyFactory::fromHex($chunks[0]->getHex(), $this->ecAdapter)]; }, function () { // Multisig - refer to the redeemScript $this->publicKeys = $this->redeemScript->getKeys(); }); }
/** * @param ScriptInterface $script */ public function __construct(ScriptInterface $script) { $this->script = $script; $chunks = $this->script->getScriptParser()->decode(); if (count($chunks) < 5 || !$chunks[2]->isPush()) { throw new \RuntimeException('Malformed pay-to-pubkey-hash script'); } /** @var Buffer $hash */ $hash = $chunks[2]->getData(); $this->hash = $hash; }
/** * @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 */ public function __construct(ScriptInterface $script) { $this->script = $script; $chunks = $script->getScriptParser()->decode(); if (count($chunks) < 1 || !$chunks[0]->isPush()) { throw new \InvalidArgumentException('Malformed pay-to-pubkey script'); } $this->publicKey = PublicKeyFactory::fromHex($chunks[0]->getData()); }
/** * @param ScriptInterface $script * @param NetworkInterface $network * @return String * @throws \RuntimeException */ public static function getAssociatedAddress(ScriptInterface $script, NetworkInterface $network = null) { $classifier = new OutputClassifier($script); $network = $network ?: Bitcoin::getNetwork(); try { if ($classifier->isPayToPublicKey()) { $address = PublicKeyFactory::fromHex($script->getScriptParser()->decode()[0]->getData())->getAddress(); } else { $address = self::fromOutputScript($script); } return Base58::encodeCheck(Buffer::hex($network->getAddressByte() . $address->getHash(), 21)); } catch (\Exception $e) { throw new \RuntimeException('No address associated with this script type'); } }
/** * @param ScriptInterface $script * @return RedeemScript */ public static function fromScript(ScriptInterface $script) { $publicKeys = []; $parse = $script->getScriptParser()->parse(); $opCodes = $script->getOpcodes(); $m = $opCodes->getOpByName($parse[0]) - $opCodes->getOpByName('OP_1') + 1; foreach (array_slice($parse, 1, -2) as $item) { if (!$item instanceof Buffer) { throw new \RuntimeException('Unable to load public key'); } $publicKeys[] = PublicKeyFactory::fromHex($item->getHex()); } if (count($publicKeys) == 0) { throw new \LogicException('No public keys found in script'); } return new self($m, $publicKeys); }
/** * @param BufferInterface $programHash * @return bool */ public function isWitness(&$programHash = null) { $buffer = $this->script->getBuffer(); $size = $buffer->getSize(); if ($size < 4 || $size > 34) { return false; } $parser = $this->script->getScriptParser(); $script = $parser->decode(); if (count($script) !== 2 || !$script[1]->isPush()) { return false; } $version = $script[0]->getOp(); if ($version != Opcodes::OP_0 && ($version < Opcodes::OP_1 || $version > Opcodes::OP_16)) { return false; } $witness = $script[1]; if ($size === $witness->getDataSize() + 2) { $programHash = $witness->getData(); return true; } return false; }
/** * @return bool */ private function run() { $math = $this->math; $this->hashStartPos = 0; $this->opCount = 0; $parser = $this->script->getScriptParser(); if ($this->script->getBuffer()->getSize() > 10000) { return false; } try { foreach ($parser as $operation) { $opCode = $operation->getOp(); $pushData = $operation->getData(); $fExec = $this->checkExec(); // 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 ($this->minimalPush && !$this->checkMinimalPush($opCode, $pushData)) { throw new ScriptRuntimeException(self::VERIFY_MINIMALDATA, 'Minimal pushdata required'); } $this->mainStack->push($pushData); // echo " - [pushed '" . $pushData->getHex() . "']\n"; } elseif ($fExec || $opCode !== Opcodes::OP_IF && $opCode !== Opcodes::OP_ENDIF) { // echo "OPCODE - " . $this->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 = $opCode - (Opcodes::OP_1 - 1); $this->mainStack->push(Number::int($num)->getBuffer()); break; case Opcodes::OP_CHECKLOCKTIMEVERIFY: if (!$this->flags->checkFlags(self::VERIFY_CHECKLOCKTIMEVERIFY)) { if ($this->flags->checkFlags(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS)) { throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); } break; } if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation - CLTV'); } $lockTime = Number::buffer($this->mainStack[-1], $this->minimalPush, 5, $math); if (!$this->checkLockTime($lockTime)) { throw new ScriptRuntimeException(self::VERIFY_CHECKLOCKTIMEVERIFY, 'Unsatisfied locktime'); } break; case Opcodes::OP_CHECKSEQUENCEVERIFY: if (!$this->flags->checkFlags(self::VERIFY_CHECKSEQUENCEVERIFY)) { if ($this->flags->checkFlags(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS)) { throw new ScriptRuntimeException(self::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOP found - this is discouraged'); } break; } if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation - CSV'); } $sequence = Number::buffer($this->mainStack[-1], $this->minimalPush, 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 (!$this->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 ($this->flags->checkFlags(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 ($this->mainStack->isEmpty()) { throw new \RuntimeException('Unbalanced conditional'); } // todo $buffer = Number::buffer($this->mainStack->pop(), $this->minimalPush)->getBuffer(); $value = $this->castToBool($buffer); if ($opCode === Opcodes::OP_NOTIF) { $value = !$value; } } $this->vfStack->push($value ? $this->vchTrue : $this->vchFalse); break; case Opcodes::OP_ELSE: if ($this->vfStack->isEmpty()) { throw new \RuntimeException('Unbalanced conditional'); } $this->vfStack[-1] = !$this->vfStack->end() ? $this->vchTrue : $this->vchFalse; break; case Opcodes::OP_ENDIF: if ($this->vfStack->isEmpty()) { throw new \RuntimeException('Unbalanced conditional'); } break; case Opcodes::OP_VERIFY: if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation'); } $value = $this->castToBool($this->mainStack[-1]); if (!$value) { throw new \RuntimeException('Error: verify'); } $this->mainStack->pop(); break; case Opcodes::OP_RESERVED: // todo break; case Opcodes::OP_TOALTSTACK: if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_TOALTSTACK'); } $this->altStack->push($this->mainStack->pop()); break; case Opcodes::OP_FROMALTSTACK: if ($this->altStack->isEmpty()) { throw new \RuntimeException('Invalid alt-stack operation OP_FROMALTSTACK'); } $this->mainStack->push($this->altStack->pop()); break; case Opcodes::OP_IFDUP: // If top value not zero, duplicate it. if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_IFDUP'); } $vch = $this->mainStack[-1]; if ($this->castToBool($vch)) { $this->mainStack->push($vch); } break; case Opcodes::OP_DEPTH: $num = count($this->mainStack); if ($num === 0) { $depth = $this->vchFalse; } else { $depth = Number::int($num)->getBuffer(); } $this->mainStack->push($depth); break; case Opcodes::OP_DROP: if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_DROP'); } $this->mainStack->pop(); break; case Opcodes::OP_DUP: if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_DUP'); } $vch = $this->mainStack[-1]; $this->mainStack->push($vch); break; case Opcodes::OP_NIP: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_NIP'); } unset($this->mainStack[-2]); break; case Opcodes::OP_OVER: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_OVER'); } $vch = $this->mainStack[-2]; $this->mainStack->push($vch); break; case Opcodes::OP_ROT: if (count($this->mainStack) < 3) { throw new \RuntimeException('Invalid stack operation OP_ROT'); } $this->mainStack->swap(-3, -2); $this->mainStack->swap(-2, -1); break; case Opcodes::OP_SWAP: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_SWAP'); } $this->mainStack->swap(-2, -1); break; case Opcodes::OP_TUCK: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_TUCK'); } $vch = $this->mainStack[-1]; $this->mainStack->add(count($this->mainStack) - 1 - 2, $vch); break; case Opcodes::OP_PICK: case Opcodes::OP_ROLL: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_PICK'); } $n = Number::buffer($this->mainStack[-1], $this->minimalPush, 4)->getInt(); $this->mainStack->pop(); if ($math->cmp($n, 0) < 0 || $math->cmp($n, count($this->mainStack)) >= 0) { throw new \RuntimeException('Invalid stack operation OP_PICK'); } $pos = (int) $math->sub($math->sub(0, $n), 1); $vch = $this->mainStack[$pos]; if ($opCode === Opcodes::OP_ROLL) { unset($this->mainStack[$pos]); } $this->mainStack->push($vch); break; case Opcodes::OP_2DROP: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_2DROP'); } $this->mainStack->pop(); $this->mainStack->pop(); break; case Opcodes::OP_2DUP: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_2DUP'); } $string1 = $this->mainStack[-2]; $string2 = $this->mainStack[-1]; $this->mainStack->push($string1); $this->mainStack->push($string2); break; case Opcodes::OP_3DUP: if (count($this->mainStack) < 3) { throw new \RuntimeException('Invalid stack operation OP_3DUP'); } $string1 = $this->mainStack[-3]; $string2 = $this->mainStack[-2]; $string3 = $this->mainStack[-1]; $this->mainStack->push($string1); $this->mainStack->push($string2); $this->mainStack->push($string3); break; case Opcodes::OP_2OVER: if (count($this->mainStack) < 4) { throw new \RuntimeException('Invalid stack operation OP_2OVER'); } $string1 = $this->mainStack[-4]; $string2 = $this->mainStack[-3]; $this->mainStack->push($string1); $this->mainStack->push($string2); break; case Opcodes::OP_2ROT: if (count($this->mainStack) < 6) { throw new \RuntimeException('Invalid stack operation OP_2ROT'); } $string1 = $this->mainStack[-6]; $string2 = $this->mainStack[-5]; unset($this->mainStack[-6], $this->mainStack[-5]); $this->mainStack->push($string1); $this->mainStack->push($string2); break; case Opcodes::OP_2SWAP: if (count($this->mainStack) < 4) { throw new \RuntimeException('Invalid stack operation OP_2SWAP'); } $this->mainStack->swap(-3, -1); $this->mainStack->swap(-4, -2); break; case Opcodes::OP_SIZE: if ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation OP_SIZE'); } // todo: Int sizes? $vch = $this->mainStack[-1]; $this->mainStack->push(Number::int($vch->getSize())->getBuffer()); break; case Opcodes::OP_EQUAL: case Opcodes::OP_EQUALVERIFY: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation OP_EQUAL'); } $vch1 = $this->mainStack[-2]; $vch2 = $this->mainStack[-1]; $equal = $vch1->getBinary() === $vch2->getBinary(); $this->mainStack->pop(); $this->mainStack->pop(); $this->mainStack->push($equal ? $this->vchTrue : $this->vchFalse); if ($opCode === Opcodes::OP_EQUALVERIFY) { if ($equal) { $this->mainStack->pop(); } else { throw new \RuntimeException('Error EQUALVERIFY'); } } break; // Arithmetic operations // Arithmetic operations case $opCode >= Opcodes::OP_1ADD && $opCode <= Opcodes::OP_0NOTEQUAL: if ($this->mainStack->isEmpty()) { throw new \Exception('Invalid stack operation 1ADD-OP_0NOTEQUAL'); } $num = Number::buffer($this->mainStack[-1], $this->minimalPush)->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); } $this->mainStack->pop(); $buffer = Number::int($num)->getBuffer(); $this->mainStack->push($buffer); break; case $opCode >= Opcodes::OP_ADD && $opCode <= Opcodes::OP_MAX: if (count($this->mainStack) < 2) { throw new \Exception('Invalid stack operation (OP_ADD - OP_MAX)'); } $num1 = Number::buffer($this->mainStack[-2], $this->minimalPush)->getInt(); $num2 = Number::buffer($this->mainStack[-1], $this->minimalPush)->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, $this->int0->getInt()) !== 0 && $math->cmp($num2, $this->int0->getInt()) !== 0; } else { if ($opCode === Opcodes::OP_BOOLOR) { $num = $math->cmp($num1, $this->int0->getInt()) !== 0 || $math->cmp($num2, $this->int0->getInt()) !== 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; } } } } $this->mainStack->pop(); $this->mainStack->pop(); $buffer = Number::int($num)->getBuffer(); $this->mainStack->push($buffer); if ($opCode === Opcodes::OP_NUMEQUALVERIFY) { if ($this->castToBool($this->mainStack[-1])) { $this->mainStack->pop(); } else { throw new \RuntimeException('NUM EQUAL VERIFY error'); } } break; case Opcodes::OP_WITHIN: if (count($this->mainStack) < 3) { throw new \RuntimeException('Invalid stack operation'); } $num1 = Number::buffer($this->mainStack[-3], $this->minimalPush)->getInt(); $num2 = Number::buffer($this->mainStack[-2], $this->minimalPush)->getInt(); $num3 = Number::buffer($this->mainStack[-1], $this->minimalPush)->getInt(); $value = $math->cmp($num2, $num1) <= 0 && $math->cmp($num1, $num3) < 0; $this->mainStack->pop(); $this->mainStack->pop(); $this->mainStack->pop(); $this->mainStack->push($value ? $this->vchFalse : $this->vchTrue); 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 ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation'); } $buffer = $this->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); } $this->mainStack->pop(); $this->mainStack->push($hash); break; case Opcodes::OP_CODESEPARATOR: $this->hashStartPos = $parser->getPosition(); break; case Opcodes::OP_CHECKSIG: case Opcodes::OP_CHECKSIGVERIFY: if (count($this->mainStack) < 2) { throw new \RuntimeException('Invalid stack operation'); } $vchPubKey = $this->mainStack[-1]; $vchSig = $this->mainStack[-2]; $scriptCode = new Script($this->script->getBuffer()->slice($this->hashStartPos)); $success = $this->checkSig($scriptCode, $vchSig, $vchPubKey); $this->mainStack->pop(); $this->mainStack->pop(); $this->mainStack->push($success ? $this->vchTrue : $this->vchFalse); if ($opCode === Opcodes::OP_CHECKSIGVERIFY) { if ($success) { $this->mainStack->pop(); } else { throw new \RuntimeException('Checksig verify'); } } break; case Opcodes::OP_CHECKMULTISIG: case Opcodes::OP_CHECKMULTISIGVERIFY: $i = 1; if (count($this->mainStack) < $i) { throw new \RuntimeException('Invalid stack operation'); } $keyCount = Number::buffer($this->mainStack[-$i], $this->minimalPush)->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($this->mainStack) < $i) { throw new \RuntimeException('Invalid stack operation'); } $sigCount = Number::buffer($this->mainStack[-$i], $this->minimalPush)->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($this->script->getBuffer()->slice($this->hashStartPos)); $fSuccess = true; while ($fSuccess && $sigCount > 0) { // Fetch the signature and public key $sig = $this->mainStack[-$isig]; $pubkey = $this->mainStack[-$ikey]; // Erase the signature and public key. unset($this->mainStack[-$isig], $this->mainStack[-$ikey]); // Decrement $i, since we are consuming stack values. $i -= 2; if ($this->checkSig($scriptCode, $sig, $pubkey)) { $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) { $this->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 ($this->mainStack->isEmpty()) { throw new \RuntimeException('Invalid stack operation'); } if ($this->flags->checkFlags(self::VERIFY_NULL_DUMMY) && $this->mainStack[-1]->getSize()) { throw new ScriptRuntimeException(self::VERIFY_NULL_DUMMY, 'Extra P2SH stack value should be OP_0'); } $this->mainStack->pop(); $this->mainStack->push($fSuccess ? $this->vchTrue : $this->vchFalse); if ($opCode === Opcodes::OP_CHECKMULTISIGVERIFY) { if ($fSuccess) { $this->mainStack->pop(); } else { throw new \RuntimeException('OP_CHECKMULTISIG verify'); } } break; default: throw new \RuntimeException('Opcode not found'); } if (count($this->mainStack) + count($this->altStack) > 1000) { throw new \RuntimeException('Invalid stack size, exceeds 1000'); } } } if (!$this->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() ; return false; } }
/** * @param ScriptInterface $script */ public function __construct(ScriptInterface $script) { $this->script = $script; $this->evalScript = $script->getScriptParser()->parse(); }
/** * @return bool */ public function run() { $math = $this->ecAdapter->getMath(); $opcodes = $this->script->getOpCodes(); $flags = $this->flags; $mainStack = $this->state->getMainStack(); $altStack = $this->state->getAltStack(); $vfStack = $this->state->getVfStack(); $this->hashStartPos = 0; $this->opCount = 0; $parser = $this->script->getScriptParser(); $_bn0 = Buffer::hex('00'); $_bn1 = Buffer::hex('01'); if ($this->script->getBuffer()->getSize() > 10000) { return false; } $checkFExec = function () use(&$vfStack) { $c = 0; $len = $vfStack->end(); for ($i = 0; $i < $len; $i++) { if ($vfStack->top(0 - $len - $i) == true) { $c++; } } return (bool) $c; }; try { while ($parser->next($opCode, $pushData) === true) { $fExec = !$checkFExec(); // If pushdata was written to, if ($pushData instanceof Buffer && $pushData->getSize() > InterpreterInterface::MAX_SCRIPT_ELEMENT_SIZE) { throw new \Exception('Error - push size'); } // OP_RESERVED should not count towards opCount if ($this->script->getOpcodes()->cmp($opCode, 'OP_16') > 0 && ++$this->opCount) { $this->checkOpcodeCount(); } if ($this->checkDisabledOpcodes) { if ($this->isDisabledOp($opCode)) { throw new \Exception('Disabled Opcode'); } } if ($fExec && $opCode >= 0 && $opcodes->cmp($opCode, 'OP_PUSHDATA4') <= 0) { // In range of a pushdata opcode if ($flags->checkFlags(InterpreterInterface::VERIFY_MINIMALDATA) && !$this->checkMinimalPush($opCode, $pushData)) { throw new ScriptRuntimeException(InterpreterInterface::VERIFY_MINIMALDATA, 'Minimal pushdata required'); } $mainStack->push($pushData); //echo " - [pushed '" . $pushData->getHex() . "']\n"; } elseif ($fExec || $opcodes->isOp($opCode, 'OP_IF') <= 0 && $opcodes->isOp($opCode, 'OP_ENDIF')) { //echo " - [". $opcodes->getOp($opCode) . "]\n"; switch ($opCode) { case $opcodes->getOpByName('OP_1NEGATE'): case $opcodes->cmp($opCode, 'OP_1') >= 0 && $opcodes->cmp($opCode, 'OP_16') <= 0: $pushInt = new PushIntOperation($opcodes); $pushInt->op($opCode, $mainStack); break; case $opcodes->cmp($opCode, 'OP_NOP1') >= 0 && $opcodes->cmp($opCode, 'OP_NOP10') <= 0: if ($flags->checkFlags(InterpreterInterface::VERIFY_DISCOURAGE_UPGRADABLE_NOPS)) { throw new ScriptRuntimeException(InterpreterInterface::VERIFY_DISCOURAGE_UPGRADABLE_NOPS, 'Upgradable NOPS found - this is discouraged'); } break; case $opcodes->getOpByName('OP_NOP'): case $opcodes->isOp($opCode, 'OP_IF') || $opcodes->isOp($opCode, 'OP_NOTIF'): case $opcodes->isOp($opCode, 'OP_ELSE') || $opcodes->isOp($opCode, 'OP_ENDIF'): case $opcodes->getOpByName('OP_VERIFY'): case $opcodes->getOpByName('OP_RETURN'): $flowControl = new FlowControlOperation($opcodes, function (Buffer $buffer) { return $this->castToBool($buffer); }); $flowControl->op($opCode, $mainStack, $vfStack, $fExec); break; case $opcodes->getOpByName('OP_RESERVED'): // todo break; case $opcodes->getOpByName('OP_TOALTSTACK'): case $opcodes->getOpByName('OP_FROMALTSTACK'): case $opcodes->cmp($opCode, 'OP_IFDUP') >= 0 && $opcodes->cmp($opCode, 'OP_TUCK') <= 0: case $opcodes->cmp($opCode, 'OP_2DROP') >= 0 && $opcodes->cmp($opCode, 'OP_2SWAP') <= 0: $stackOper = new StackOperation($opcodes, $this->ecAdapter->getMath(), function (Buffer $buffer) { return $this->castToBool($buffer); }); $stackOper->op($opCode, $mainStack, $altStack); break; case $opcodes->getOpByName('OP_SIZE'): if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation OP_SIZE'); } // todo: Int sizes? $vch = $mainStack->top(-1); $size = Buffer::hex($math->decHex($vch->getSize())); $mainStack->push($size); break; case $opcodes->getOpByName('OP_EQUAL'): // cscriptnum // cscriptnum case $opcodes->getOpByName('OP_EQUALVERIFY'): //case $this->isOp($opCode, 'OP_NOTEQUAL'): // use OP_NUMNOTEQUAL if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation OP_EQUAL'); } $vch1 = $mainStack->top(-2); $vch2 = $mainStack->top(-1); $equal = $vch1->getBinary() === $vch2->getBinary(); // OP_NOTEQUAL is disabled //if ($this->isOp($opCode, 'OP_NOTEQUAL')) { // $equal = !$equal; //} $mainStack->pop(); $mainStack->pop(); $mainStack->push($equal ? $_bn1 : $_bn0); if ($opcodes->isOp($opCode, 'OP_EQUALVERIFY')) { if ($equal) { $mainStack->pop(); } else { throw new \Exception('Error EQUALVERIFY'); } } break; // Arithmetic operations // Arithmetic operations case $opcodes->cmp($opCode, 'OP_1ADD') >= 0 && $opcodes->cmp($opCode, 'OP_WITHIN') <= 0: $arithmetic = new ArithmeticOperation($opcodes, $this->ecAdapter->getMath(), function (Buffer $buffer) { return $this->castToBool($buffer); }, $_bn0, $_bn1); $arithmetic->op($opCode, $mainStack); break; // Hash operations // Hash operations case $opcodes->cmp($opCode, 'OP_RIPEMD160') >= 0 && $opcodes->cmp($opCode, 'OP_HASH256') <= 0: $hash = new HashOperation($opcodes); $hash->op($opCode, $mainStack); break; case $opcodes->getOpByName('OP_CODESEPARATOR'): $this->hashStartPos = $parser->getPosition(); break; case $opcodes->getOpByName('OP_CHECKSIG'): case $opcodes->getOpByName('OP_CHECKSIGVERIFY'): if ($mainStack->size() < 2) { throw new \Exception('Invalid stack operation'); } $vchPubKey = $mainStack->top(-1); $vchSig = $mainStack->top(-2); $scriptCode = new Script($this->script->getBuffer()->slice($this->hashStartPos)); $success = $this->checkSig($scriptCode, $vchSig, $vchPubKey); $mainStack->pop(); $mainStack->pop(); $mainStack->push($success ? $_bn1 : $_bn0); if ($opcodes->isOp($opCode, 'OP_CHECKSIGVERIFY')) { if ($success) { $mainStack->pop(); } else { throw new \Exception('Checksig verify'); } } break; case $opcodes->getOpByName('OP_CHECKMULTISIG'): case $opcodes->getOpByName('OP_CHECKMULTISIGVERIFY'): $i = 1; if ($mainStack->size() < $i) { throw new \Exception('Invalid stack operation'); } $math = $this->ecAdapter->getMath(); $keyCount = $mainStack->top(-$i)->getInt(); if ($math->cmp($keyCount, 0) < 0 || $math->cmp($keyCount, 20) > 0) { throw new \Exception('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; if ($mainStack->size() < $i) { throw new \Exception('Invalid stack operation'); } $sigCount = $mainStack->top(-$i)->getInt(); // cscriptnum if ($math->cmp($sigCount, 0) < 0 || $math->cmp($sigCount, $keyCount) > 0) { throw new \Exception('Invalid Signature count'); } $isig = ++$i; $i += $sigCount; // Extract the script since the last OP_CODESEPARATOR $scriptCode = new Script($this->script->getBuffer()->slice($this->hashStartPos)); $fSuccess = true; while ($fSuccess && $sigCount > 0) { // Fetch the signature and public key $sig = $mainStack->top(-$isig); $pubkey = $mainStack->top(-$ikey); // Erase the signature and public key. $mainStack->erase(-$isig); $mainStack->erase(-$ikey); // Decrement $i, since we are consuming stack values. $i -= 2; if ($this->checksig($scriptCode, $sig, $pubkey)) { $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->size() < 1) { throw new \Exception('Invalid stack operation'); } if ($flags->checkFlags(InterpreterInterface::VERIFY_NULL_DUMMY) && $mainStack->top(-1)->getSize()) { throw new ScriptRuntimeException(InterpreterInterface::VERIFY_NULL_DUMMY, 'Extra P2SH stack value should be OP_0'); } $mainStack->pop(); $mainStack->push($fSuccess ? $_bn1 : $_bn0); if ($opcodes->isOp($opCode, 'OP_CHECKMULTISIGVERIFY')) { if ($fSuccess) { $mainStack->pop(); } else { throw new \Exception('OP_CHECKMULTISIG verify'); } } break; default: throw new \Exception('Opcode not found'); } if ($mainStack->size() + $altStack->size() > 1000) { throw new \Exception('Invalid stack size, exceeds 1000'); } } } if (!$vfStack->end() == 0) { throw new \Exception('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() ; return false; } }
/** * @param ScriptInterface $scriptSig * @return int */ public function countP2shSigOps(ScriptInterface $scriptSig) { if (!ScriptFactory::scriptPubKey()->classify($this)->isPayToScriptHash()) { return $this->countSigOps(true); } $parser = $scriptSig->getScriptParser(); $data = null; foreach ($parser as $exec) { if ($exec->getOp() > Opcodes::OP_16) { return 0; } if ($exec->isPush()) { $data = $exec->getData(); } } if (!$data instanceof BufferInterface) { return 0; } return (new Script($data))->countSigOps(true); }
/** * @param ScriptInterface $script */ public function __construct(ScriptInterface $script) { $this->decoded = $script->getScriptParser()->decode(); }