/** * @param $opCode * @param ScriptStack $mainStack * @throws \BitWasp\Bitcoin\Exceptions\ScriptStackException * @throws \Exception */ public function op($opCode, ScriptStack $mainStack) { if ($mainStack->size() < 1) { throw new \Exception('Invalid stack operation'); } $opCodes = $this->opCodes; $opName = $opCodes->getOp($opCode); $buffer = $mainStack->top(-1); if ($opCodes->cmp($opCode, 'OP_RIPEMD160') >= 0 && $opCodes->cmp($opCode, 'OP_HASH256') <= 0) { if ($opName == 'OP_RIPEMD160') { $hash = Hash::ripemd160($buffer); } elseif ($opName == 'OP_SHA1') { $hash = Hash::sha1($buffer); } elseif ($opName == 'OP_SHA256') { $hash = Hash::sha256($buffer); } elseif ($opName == 'OP_HASH160') { $hash = Hash::sha256ripe160($buffer); } else { // is hash256 $hash = Hash::sha256d($buffer); } $mainStack->pop(); $mainStack->push($hash); } else { throw new \Exception('Opcode not found'); } }
public function getMockMessage($command, $invalidChecksum = false) { $payload = $this->getMockPayload($command); $net = Bitcoin::getDefaultNetwork(); $msg = $this->getMock('BitWasp\\Bitcoin\\Networking\\NetworkMessage', ['getCommand', 'getPayload', 'getChecksum'], [$net, $payload]); $msg->expects($this->atLeastOnce())->method('getCommand')->willReturn($command); $msg->expects($this->atLeastOnce())->method('getPayload')->willReturn($payload); if ($invalidChecksum) { $random = new Random(); $bytes = $random->bytes(4); $msg->expects($this->atLeastOnce())->method('getChecksum')->willReturn($bytes); } else { $msg->expects($this->atLeastOnce())->method('getChecksum')->willReturn(Hash::sha256d(new Buffer())->slice(0, 4)); } return $msg; }
/** * @param BlockInterface $block * @param TransactionSerializerInterface $txSerializer * @return BlockData */ public function parseUtxos(BlockInterface $block, TransactionSerializerInterface $txSerializer) { $blockData = new BlockData(); $unknown = []; $hashStorage = new HashStorage(); // Record every Outpoint required for the block. foreach ($block->getTransactions() as $t => $tx) { if ($tx->isCoinbase()) { continue; } foreach ($tx->getInputs() as $in) { $outpoint = $in->getOutPoint(); $unknown[$outpoint->getTxId()->getBinary() . $outpoint->getVout()] = $outpoint; } } foreach ($block->getTransactions() as $tx) { /** @var BufferInterface $buffer */ $buffer = $txSerializer->serialize($tx); $hash = Hash::sha256d($buffer)->flip(); $hashStorage->attach($tx, $hash); $hashBin = $hash->getBinary(); foreach ($tx->getOutputs() as $i => $out) { $lookup = $hashBin . $i; if (isset($unknown[$lookup])) { // Remove unknown outpoints which consume this output $outpoint = $unknown[$lookup]; $utxo = new Utxo($outpoint, $out); unset($unknown[$lookup]); } else { // Record new utxos which are not consumed in the same block $utxo = new Utxo(new OutPoint($hash, $i), $out); $blockData->remainingNew[] = $utxo; } // All utxos produced are stored $blockData->parsedUtxos[] = $utxo; } } $blockData->requiredOutpoints = array_values($unknown); $blockData->hashStorage = $hashStorage; return $blockData; }
/** * @param BlockHeaderInterface[] $headers * @return HeadersBatch */ public function prepareBatch(array $headers) { $countHeaders = count($headers); if (0 === $countHeaders) { return new HeadersBatch($this->chains->best($this->math), []); } $bestPrev = null; $firstUnknown = null; $hashStorage = new HashStorage(); foreach ($headers as $i => &$head) { if ($this->chains->isKnownHeader($head->getPrevBlock())) { $bestPrev = $head->getPrevBlock(); } $hash = Hash::sha256d($head->getBuffer())->flip(); $hashStorage->attach($head, $hash); if ($firstUnknown === null && !$this->chains->isKnownHeader($hash)) { $firstUnknown = $i; } } if (!$bestPrev instanceof BufferInterface) { throw new \RuntimeException('Headers::accept(): Unknown start header'); } $view = $this->chains->isTip($bestPrev); if ($view === false) { throw new \RuntimeException('Headers::accept(): Unhandled fork'); } $prevIndex = $view->getIndex(); $access = $this->chains->access($view); $batch = []; if ($firstUnknown !== null) { $versionInfo = $this->db->findSuperMajorityInfoByView($view); $forks = new Forks($this->consensus->getParams(), $prevIndex, $versionInfo); for ($i = $firstUnknown; $i < $countHeaders; $i++) { /** * @var BufferInterface $hash * @var BlockHeaderInterface $header */ $header = $headers[$i]; $hash = $hashStorage[$header]; $this->headerCheck->check($hash, $header); $index = $this->getNextIndex($hash, $prevIndex, $header); $forks->next($index); $this->headerCheck->checkContextual($access, $index, $prevIndex, $forks); $batch[] = $index; $prevIndex = $index; } } return new HeadersBatch($view, $batch); }
/** * @return Buffer */ public function getHash() { return Hash::sha256d($this->getBuffer())->flip(); }
/** * Calculate the hash of the current transaction, when you are looking to * spend $txOut, and are signing $inputToSign. The SigHashType defaults to * SIGHASH_ALL, though SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ANYONECANPAY * can be used. * * @param ScriptInterface $txOutScript * @param int $inputToSign * @param int $sighashType * @return BufferInterface * @throws \Exception */ public function calculate(ScriptInterface $txOutScript, $inputToSign, $sighashType = SigHash::ALL) { $sighashType = (int) $sighashType; $hashPrevOuts = $this->hashPrevOuts($sighashType); $hashSequence = $this->hashSequences($sighashType); $hashOutputs = $this->hashOutputs($sighashType, $inputToSign); $input = $this->transaction->getInput($inputToSign); return Hash::sha256d(new Buffer(pack("V", $this->transaction->getVersion()) . $hashPrevOuts->getBinary() . $hashSequence->getBinary() . $input->getOutPoint()->getBinary() . ScriptFactory::create()->push($txOutScript->getBuffer())->getScript()->getBinary() . Buffer::int($this->amount, 8)->flip()->getBinary() . pack("V", $input->getSequence()) . $hashOutputs->getBinary() . pack("V", $this->transaction->getLockTime()) . pack("V", $sighashType))); }
/** * @param string $message * @return \BitWasp\Buffertools\Buffer */ public function calculateMessageHash($message) { $content = new Buffer("Bitcoin Signed Message:\n" . Buffertools::numToVarInt(strlen($message))->getBinary() . $message); $hash = Hash::sha256d($content); return $hash; }
/** * @param Parser $parser * @return NetworkMessage * @throws \BitWasp\Buffertools\Exceptions\ParserOutOfRange * @throws \Exception */ public function fromParser(Parser $parser) { list($netBytes, $command, $payloadSize, $checksum) = $this->getHeaderTemplate()->parse($parser); /** @var Buffer $netBytes */ /** @var Buffer $command */ /** @var int|string $payloadSize */ /** @var Buffer $checksum */ if ($netBytes->getHex() !== $this->network->getNetMagicBytes()) { throw new \RuntimeException('Invalid magic bytes for network'); } $buffer = $payloadSize > 0 ? $parser->readBytes($payloadSize) : new Buffer('', 0, $this->math); // Compare payload checksum against header value if (Hash::sha256d($buffer)->slice(0, 4)->getBinary() !== $checksum->getBinary()) { throw new \RuntimeException('Invalid packet checksum'); } $cmd = trim($command->getBinary()); switch ($cmd) { case Message::VERSION: $payload = $this->versionSerializer->parse($buffer); break; case Message::VERACK: $payload = new VerAck(); break; case Message::SENDHEADERS: $payload = new SendHeaders(); break; case Message::ADDR: $payload = $this->addrSerializer->parse($buffer); break; case Message::INV: $payload = $this->invSerializer->parse($buffer); break; case Message::GETDATA: $payload = $this->getDataSerializer->parse($buffer); break; case Message::NOTFOUND: $payload = $this->notFoundSerializer->parse($buffer); break; case Message::GETBLOCKS: $payload = $this->getBlocksSerializer->parse($buffer); break; case Message::GETHEADERS: $payload = $this->getHeadersSerializer->parse($buffer); break; case Message::TX: $payload = new Tx($this->txSerializer->parse($buffer)); break; case Message::BLOCK: $payload = new Block($this->blockSerializer->parse($buffer)); break; case Message::HEADERS: $payload = $this->headersSerializer->parse($buffer); break; case Message::GETADDR: $payload = new GetAddr(); break; case Message::MEMPOOL: $payload = new MemPool(); break; case Message::FEEFILTER: $payload = $this->feeFilterSerializer->parse($buffer); break; case Message::FILTERLOAD: $payload = $this->filterLoadSerializer->parse($buffer); break; case Message::FILTERADD: $payload = $this->filterAddSerializer->parse($buffer); break; case Message::FILTERCLEAR: $payload = new FilterClear(); break; case Message::MERKLEBLOCK: $payload = $this->merkleBlockSerializer->parse($buffer); break; case Message::PING: $payload = $this->pingSerializer->parse($buffer); break; case Message::PONG: $payload = $this->pongSerializer->parse($buffer); break; case Message::REJECT: $payload = $this->rejectSerializer->parse($buffer); break; case Message::ALERT: $payload = $this->alertSerializer->parse($buffer); break; default: throw new \RuntimeException('Invalid command'); } return new NetworkMessage($this->network, $payload); }
/** * @return Buffer */ public function getTxHash() { return Hash::sha256d($this->getBuffer()); }
/** * @param TransactionInterface|null $coinbaseTx * @return Block * @throws \BitWasp\Bitcoin\Exceptions\MerkleTreeEmpty */ public function run(TransactionInterface $coinbaseTx = null) { $nonce = '0'; $maxNonce = $this->math->pow(2, 32); // Allow user supplied transactions if ($coinbaseTx == null) { $coinbaseTx = new Transaction(); $coinbaseTx->getInputs()->addInput(new TransactionInput('0000000000000000000000000000000000000000000000000000000000000000', 4294967295.0)); $coinbaseTx->getOutputs()->addOutput(new TransactionOutput(5000000000.0, $this->script)); } $inputs = $coinbaseTx->getInputs(); $found = false; $usingDiff = $this->lastBlockHeader->getBits(); $diff = new Difficulty($this->math); $target = $diff->getTarget($usingDiff); while (false === $found) { // Set coinbase script, and build Merkle tree & block header. $inputs->getInput(0)->setScript($this->getCoinbaseScriptBuf()); $transactions = new TransactionCollection(array_merge(array($coinbaseTx), $this->transactions->getTransactions())); $merkleRoot = new MerkleRoot($this->math, $transactions); $merkleHash = $merkleRoot->calculateHash(); $header = new BlockHeader($this->version, $this->lastBlockHeader->getBlockHash(), $merkleHash, $this->timestamp, $usingDiff, '0'); $t = microtime(true); // Loop through all nonces (up to 2^32). Restart after modifying extranonce. while ($this->math->cmp($header->getNonce(), $maxNonce) <= 0) { $header->setNonce($this->math->add($header->getNonce(), '1')); $hash = (new Parser())->writeBytes(32, Hash::sha256d($header->getBuffer()), true)->getBuffer(); if ($this->math->cmp($hash->getInt(), $target) <= 0) { $block = new Block($this->math, $header, $transactions); return $block; } if ($this->report && $this->math->cmp($this->math->mod($header->getNonce(), 100000), '0') == 0) { $time = microtime(true) - $t; $khash = $nonce / $time / 1000; echo "extraNonce[{$this->extraNonce}] nonce[{$nonce}] time[{$time}] khash/s[{$khash}] \n"; } } // Whenever we exceed 2^32, increment extraNonce and reset $nonce $this->extraNonce++; $nonce = '0'; } }
/** * @param string $message * @return \BitWasp\Buffertools\BufferInterface */ public function calculateMessageHash($message) { return Hash::sha256d($this->calculateBody($message)); }
/** * @return string */ public function getTransactionId() { $hash = bin2hex(Buffertools::flipBytes(Hash::sha256d($this->getBuffer()))); return $hash; }
/** * @return \BitWasp\Buffertools\BufferInterface */ public function getWitnessTxId() { return Hash::sha256d($this->getWitnessBuffer())->flip(); }
/** * @return BufferInterface */ public function getChecksum() { $data = $this->getPayload()->getBuffer(); return Hash::sha256d($data)->slice(0, 4); }
/** * Calculate the hash of the current transaction, when you are looking to * spend $txOut, and are signing $inputToSign. The SigHashType defaults to * SIGHASH_ALL, though SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ANYONECANPAY * can be used. * * @param ScriptInterface $txOutScript * @param $inputToSign * @param int $sighashType * @return Buffer * @throws \Exception */ public function calculate(ScriptInterface $txOutScript, $inputToSign, $sighashType = SignatureHashInterface::SIGHASH_ALL) { $copy = $this->transaction->makeCopy(); $inputs = $copy->getInputs(); $outputs = $copy->getOutputs(); if ($inputToSign > count($inputs)) { throw new \Exception('Input does not exist'); } // Default SIGHASH_ALL procedure: null all input scripts $inputCount = count($inputs); for ($i = 0; $i < $inputCount; $i++) { $inputs->getInput($i)->setScript(new Script()); } $inputs->getInput($inputToSign)->setScript($txOutScript); $math = Bitcoin::getMath(); if ($math->bitwiseAnd($sighashType, 31) == SignatureHashInterface::SIGHASH_NONE) { // Set outputs to empty vector, and set sequence number of inputs to 0. $copy->setOutputs(new TransactionOutputCollection()); // Let the others update at will. Set sequence of inputs we're not signing to 0. $inputCount = count($inputs); for ($i = 0; $i < $inputCount; $i++) { if ($math->cmp($i, $inputToSign) !== 0) { $inputs->getInput($i)->setSequence(0); } } } elseif ($math->bitwiseAnd($sighashType, 31) == SignatureHashInterface::SIGHASH_SINGLE) { // Resize output array to $inputToSign + 1, set remaining scripts to null, // and set sequence's to zero. $nOutput = $inputToSign; if ($math->cmp($nOutput, count($outputs)) >= 0) { return Buffer::hex('0100000000000000000000000000000000000000000000000000000000000000'); } // Resize.. $outputs = $outputs->slice(0, $nOutput + 1)->getOutputs(); // Set to null for ($i = 0; $i < $nOutput; $i++) { $outputs[$i] = new TransactionOutput($math->getBinaryMath()->getTwosComplement(-1, 64), new Script()); } $copy->setOutputs(new TransactionOutputCollection($outputs)); // Let the others update at will. Set sequence of inputs we're not signing to 0. $inputCount = count($inputs); for ($i = 0; $i < $inputCount; $i++) { if ($math->cmp($i, $inputToSign) != 0) { $inputs->getInput($i)->setSequence(0); } } } // This can happen regardless of whether it's ALL, NONE, or SINGLE if ($math->bitwiseAnd($sighashType, SignatureHashInterface::SIGHASH_ANYONECANPAY)) { $input = $inputs->getInput($inputToSign); $copy->setInputs(new TransactionInputCollection([$input])); } // Serialize the TxCopy and append the 4 byte hashtype (little endian); $txParser = new Parser($copy->getBuffer()); $txParser->writeInt(4, $sighashType, true); return Hash::sha256d($txParser->getBuffer()); }
/** * Calculate a checksum for the given data * * @param BufferInterface $data * @return BufferInterface */ public static function checksum(BufferInterface $data) { return Hash::sha256d($data)->slice(0, 4); }
/** * {@inheritdoc} * @see \BitWasp\Bitcoin\Block\BlockHeaderInterface::getBlockHash() */ public function getBlockHash() { $parser = new Parser(); return $parser->writeBytes(32, Hash::sha256d($this->getBuffer()), true)->getBuffer()->getHex(); }
/** * @param int|string $sequence * @param bool $change * @return int|string */ public function getSequenceOffset($sequence, $change = false) { return Hash::sha256d(new Buffer(sprintf("%s:%s:%s", $sequence, $change ? '1' : '0', $this->getMPK()->getBinary())))->getInt(); }
/** * @param Parser $parser * @return NetworkMessage * @throws \BitWasp\Buffertools\Exceptions\ParserOutOfRange * @throws \Exception */ public function fromParser(Parser &$parser) { list($netBytes, $command, $payloadSize, $checksum) = $this->getHeaderTemplate()->parse($parser); /** @var Buffer $netBytes */ /** @var Buffer $command */ /** @var int|string $payloadSize */ /** @var Buffer $checksum */ if ($netBytes->getHex() !== $this->network->getNetMagicBytes()) { throw new \RuntimeException('Invalid magic bytes for network'); } $buffer = $payloadSize > 0 ? $parser->readBytes($payloadSize) : new Buffer(); // Compare payload checksum against header value if (Hash::sha256d($buffer)->slice(0, 4)->getBinary() !== $checksum->getBinary()) { throw new \RuntimeException('Invalid packet checksum'); } $cmd = trim($command->getBinary()); switch ($cmd) { case 'version': $payload = $this->versionSerializer->parse($buffer); break; case 'verack': $payload = new VerAck(); break; case 'addr': $payload = $this->addrSerializer->parse($buffer); break; case 'inv': $payload = $this->invSerializer->parse($buffer); break; case 'getdata': $payload = $this->getDataSerializer->parse($buffer); break; case 'notfound': $payload = $this->notFoundSerializer->parse($buffer); break; case 'getblocks': $payload = $this->getBlocksSerializer->parse($buffer); break; case 'getheaders': $payload = $this->getHeadersSerializer->parse($buffer); break; case 'tx': $payload = new Tx($this->txSerializer->parse($buffer)); break; case 'block': $payload = new Block($this->blockSerializer->parse($buffer)); break; case 'headers': $payload = $this->headersSerializer->parse($buffer); break; case 'getaddr': $payload = new GetAddr(); break; case 'mempool': $payload = new MemPool(); break; case 'filterload': $payload = $this->filterLoadSerializer->parse($buffer); break; case 'filteradd': $payload = $this->filterAddSerializer->parse($buffer); break; case 'filterclear': $payload = new FilterClear(); break; case 'merkleblock': $payload = $this->merkleBlockSerializer->parse($buffer); break; case 'ping': $payload = $this->pingSerializer->parse($buffer); break; case 'pong': $payload = $this->pongSerializer->parse($buffer); break; case 'reject': $payload = $this->rejectSerializer->parse($buffer); break; case 'alert': $payload = $this->alertSerializer->parse($buffer); break; default: throw new \RuntimeException('Invalid command'); } return new NetworkMessage($this->network, $payload); }
/** * Calculate the hash of the current transaction, when you are looking to * spend $txOut, and are signing $inputToSign. The SigHashType defaults to * SIGHASH_ALL, though SIGHASH_SINGLE, SIGHASH_NONE, SIGHASH_ANYONECANPAY * can be used. * * @param ScriptInterface $txOutScript * @param int $inputToSign * @param int $sighashType * @return BufferInterface * @throws \Exception */ public function calculate(ScriptInterface $txOutScript, $inputToSign, $sighashType = SigHash::ALL) { $math = Bitcoin::getMath(); $tx = new TxMutator($this->transaction); $inputs = $tx->inputsMutator(); $outputs = $tx->outputsMutator(); // Default SIGHASH_ALL procedure: null all input scripts foreach ($inputs as $input) { $input->script(new Script()); } $inputs[$inputToSign]->script($txOutScript); if ($math->cmp($math->bitwiseAnd($sighashType, 31), SigHash::NONE) === 0) { // Set outputs to empty vector, and set sequence number of inputs to 0. $outputs->null(); // Let the others update at will. Set sequence of inputs we're not signing to 0. foreach ($inputs as $i => $input) { if ($i !== $inputToSign) { $input->sequence(0); } } } elseif ($math->cmp($math->bitwiseAnd($sighashType, 31), SigHash::SINGLE) === 0) { // Resize output array to $inputToSign + 1, set remaining scripts to null, // and set sequence's to zero. $nOutput = $inputToSign; if ($nOutput >= $this->nOutputs) { return Buffer::hex('0100000000000000000000000000000000000000000000000000000000000000', 32, $math); } // Resize, set to null $outputs->slice(0, $nOutput + 1); for ($i = 0; $i < $nOutput; $i++) { $outputs[$i]->null(); } // Let the others update at will. Set sequence of inputs we're not signing to 0 foreach ($inputs as $i => $input) { if ($i !== $inputToSign) { $input->sequence(0); } } } // This can happen regardless of whether it's ALL, NONE, or SINGLE if ($math->cmp($math->bitwiseAnd($sighashType, SigHash::ANYONECANPAY), 0) > 0) { $input = $inputs[$inputToSign]->done(); $inputs->null()->add($input); } return Hash::sha256d(Buffertools::concat($tx->done()->getBuffer(), Buffertools::flipBytes(Buffer::int($sighashType, 4, $math)))); }
/** * Traverse the Merkle Tree hashes and extract those which have a matching bit. * * @param int $height * @param int $position * @param int $nBitsUsed * @param int $nHashUsed * @param BufferInterface[] $vMatch * @return BufferInterface */ public function traverseAndExtract($height, $position, &$nBitsUsed, &$nHashUsed, &$vMatch) { if ($nBitsUsed >= count($this->vFlagBits)) { $this->fBad = true; return new Buffer(); } $parent = $this->vFlagBits[$nBitsUsed++]; if (0 === $height || !$parent) { if ($nHashUsed >= count($this->vHashes)) { $this->fBad = true; return new Buffer(); } $hash = $this->vHashes[$nHashUsed++]; if ($height === 0 && $parent) { $vMatch[] = $hash->flip(); } return $hash; } else { $left = $this->traverseAndExtract($height - 1, $position * 2, $nBitsUsed, $nHashUsed, $vMatch); if ($position * 2 + 1 < $this->calcTreeWidth($height - 1)) { $right = $this->traverseAndExtract($height - 1, $position * 2 + 1, $nBitsUsed, $nHashUsed, $vMatch); if ($right === $left) { $this->fBad = true; } } else { $right = $left; } return Hash::sha256d(Buffertools::concat($left, $right)); } }
/** * @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; } }