/** * @param $opCode * @param Buffer $pushData * @return bool */ public function next(&$opCode, &$pushData) { $opcodes = $this->script->getOpcodes(); $opCode = $opcodes->getOpByName('OP_INVALIDOPCODE'); if ($this->math->cmp($this->getPosition(), $this->getEndPos()) >= 0) { return false; } $opCode = $this->getNextOp(); if ($opcodes->cmp($opCode, 'OP_PUSHDATA4') <= 0) { if ($opcodes->cmp($opCode, 'OP_PUSHDATA1') < 0) { $size = $opCode; } else { if ($opcodes->isOp($opCode, 'OP_PUSHDATA1')) { $size = $this->unpackSize("C", 1); } else { if ($opcodes->isOp($opCode, 'OP_PUSHDATA2')) { $size = $this->unpackSize("v", 2); } else { $size = $this->unpackSize("V", 4); } } } if ($size === false || $this->validateSize($size) === false) { return false; } $pushData = new Buffer(substr($this->scriptRaw, $this->ptr, $size), $size); $this->ptr += $size; } return true; }
/** * @return bool */ public function isMultisig() { $opCodes = $this->script->getOpcodes(); $count = count($this->evalScript); if ($count <= 3) { return false; } $mOp = $this->evalScript[0]; $nOp = $this->evalScript[$count - 2]; $lastOp = $this->evalScript[$count - 1]; $keys = array_slice($this->evalScript, 1, -2); $keysValid = function () use($keys) { $valid = true; foreach ($keys as $key) { $valid &= $key instanceof Buffer && PublicKey::isCompressedOrUncompressed($key); } return $valid; }; return $count >= 2 && is_string($mOp) && is_string($nOp) && is_string($lastOp) && $opCodes->cmp($opCodes->getOpByName($mOp), 'OP_0') >= 0 && $opCodes->cmp($opCodes->getOpByName($nOp), 'OP_16') <= 0 && $this->evalScript[$count - 1] == 'OP_CHECKMULTISIG' && $keysValid(); }
/** * @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); }
/** * @return string */ public function getHumanReadable() { return implode(' ', array_map(function (Operation $operation) { return $operation->isPush() ? $operation->getData()->getHex() : $this->script->getOpcodes()->getOp($operation->getOp()); }, $this->decode())); }
/** * @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; } }