/** * returns a mix of Buffer objects and strings * * @return Buffer[]|string[] */ public function parse() { $data = array(); while ($this->next($opCode, $pushData)) { if ($opCode < 1) { $push = Buffer::hex('00'); } elseif ($opCode <= 78) { $push = $pushData; } else { // None of these are pushdatas, so just an opcode $push = $this->script->getOpCodes()->getOp($opCode); } $data[] = $push; } $this->resetPosition(); return $data; }
/** * @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; } }