/** * @param Math $math * @param TransactionInterface $transaction * @return int|string */ public function getValueIn(Math $math, TransactionInterface $transaction) { $value = 0; foreach ($transaction->getInputs() as $input) { $value = $math->add($value, $this->fetchByInput($input)->getOutput()->getValue()); } return $value; }
/** * @param UtxoView $utxoView * @param TransactionInterface $tx * @param Flags $flags * @return bool */ public function check(UtxoView $utxoView, TransactionInterface $tx, Flags $flags) { $scripts = []; foreach ($tx->getInputs() as $input) { $scripts[] = $utxoView->fetchByInput($input)->getOutput()->getScript()->getHex(); } return $this->dispatch($tx, $flags, $scripts); }
/** * 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()); }
/** * @param TransactionInterface $tx */ private function saveOutputs(TransactionInterface $tx) { $txid = $tx->getTransactionId(); $vout = 0; foreach ($tx->getOutputs()->getOutputs() as $output) { $this->contents->save($this->cacheIndex($txid, $vout), new Utxo($txid, $vout++, $output)); } $this->size += $vout; }
/** * @param UtxoView $utxoView * @param TransactionInterface $tx * @param Flags $flags * @return bool */ public function check(UtxoView $utxoView, TransactionInterface $tx, Flags $flags) { $result = true; $consensus = $this->consensus->getConsensus($flags); for ($i = 0, $c = count($tx->getInputs()); $i < $c; $i++) { $result &= $consensus->verify($tx, $utxoView->fetchByInput($tx->getInput($i))->getOutput()->getScript(), $i); } return $result; }
/** * @param ConsensusInterface $consensus * @param TransactionOutputInterface[] $outputs * @return bool */ public function checkSignatures(ConsensusInterface $consensus, array $outputs) { if (count($this->transaction->getInputs()) !== count($outputs)) { throw new \InvalidArgumentException('Incorrect scriptPubKey count'); } $result = true; foreach ($outputs as $i => $txOut) { $result = $result && $this->checkSignature($consensus, $i, $txOut); } return $result; }
/** * @param UtxoView $utxoView * @param TransactionInterface $tx * @return ScriptValidationInterface */ public function queue(UtxoView $utxoView, TransactionInterface $tx) { $hex = $tx->getHex(); $t = ['txid' => spl_object_hash($tx), 'tx' => $hex, 'scripts' => []]; for ($i = 0, $c = count($tx->getInputs()); $i < $c; $i++) { $output = $utxoView->fetchByInput($tx->getInput($i))->getOutput(); $witness = isset($tx->getWitnesses()[$i]) ? $tx->getWitness($i) : null; $t['scripts'][] = $output->getScript()->getHex(); } $this->results[] = $t; return $this; }
/** * @return $this */ public function extractSignatures() { $type = (new OutputClassifier($this->txOut->getScript()))->classify(); $scriptPubKey = $this->txOut->getScript(); $scriptSig = $this->tx->getInput($this->nInput)->getScript(); if ($type === OutputClassifier::PAYTOPUBKEYHASH || $type === OutputClassifier::PAYTOPUBKEY || $type === OutputClassifier::MULTISIG) { $values = []; foreach ($scriptSig->getScriptParser()->decode() as $o) { $values[] = $o->getData(); } $this->extractFromValues($type, $scriptPubKey, $values, 0); } if ($type === OutputClassifier::PAYTOSCRIPTHASH) { $decodeSig = $scriptSig->getScriptParser()->decode(); if (count($decodeSig) > 0) { $redeemScript = new Script(end($decodeSig)->getData()); $p2shType = (new OutputClassifier($redeemScript))->classify(); if (count($decodeSig) > 1) { $decodeSig = array_slice($decodeSig, 0, -1); } $internalSig = []; foreach ($decodeSig as $operation) { $internalSig[] = $operation->getData(); } $this->redeemScript = $redeemScript; $this->extractFromValues($p2shType, $redeemScript, $internalSig, 0); $type = $p2shType; } } $witnesses = $this->tx->getWitnesses(); if ($type === OutputClassifier::WITNESS_V0_KEYHASH) { $this->requiredSigs = 1; if (isset($witnesses[$this->nInput])) { $witness = $witnesses[$this->nInput]; $this->signatures = [TransactionSignatureFactory::fromHex($witness[0], $this->ecAdapter)]; $this->publicKeys = [PublicKeyFactory::fromHex($witness[1], $this->ecAdapter)]; } } else { if ($type === OutputClassifier::WITNESS_V0_SCRIPTHASH) { if (isset($witnesses[$this->nInput])) { $witness = $witnesses[$this->nInput]; $witCount = count($witnesses[$this->nInput]); if ($witCount > 0) { $witnessScript = new Script($witness[$witCount - 1]); $vWitness = $witness->all(); if (count($vWitness) > 1) { $vWitness = array_slice($witness->all(), 0, -1); } $witnessType = (new OutputClassifier($witnessScript))->classify(); $this->extractFromValues($witnessType, $witnessScript, $vWitness, 1); $this->witnessScript = $witnessScript; } } } } return $this; }
/** * 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 ScriptInterface $script * @param Buffer $sigBuf * @param Buffer $keyBuf * @return bool * @throws ScriptRuntimeException * @throws \Exception */ private function checkSig(ScriptInterface $script, Buffer $sigBuf, Buffer $keyBuf) { $this->checkSignatureEncoding($sigBuf)->checkPublicKeyEncoding($keyBuf); try { $txSignature = TransactionSignatureFactory::fromHex($sigBuf->getHex()); $publicKey = PublicKeyFactory::fromHex($keyBuf->getHex()); $sigHash = $this->transaction->getSignatureHash()->calculate($script, $this->inputToSign, $txSignature->getHashType()); return $this->ecAdapter->verify($sigHash, $publicKey, $txSignature->getSignature()); } catch (\Exception $e) { return false; } }
/** * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence * @return bool */ private function checkSequence(Number $sequence) { $txSequence = $this->transaction->getInput($this->inputToSign)->getSequence(); if ($this->transaction->getVersion() < 2) { return false; } if ($this->math->cmp($this->math->bitwiseAnd($txSequence, TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG), 0) !== 0) { return 0; } $mask = $this->math->bitwiseOr(TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG, TransactionInputInterface::SEQUENCE_LOCKTIME_MASK); return $this->verifyLockTime($this->math->bitwiseAnd($txSequence, $mask), TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG, Number::int($this->math->bitwiseAnd($sequence->getInt(), $mask))); }
/** * @param \BitWasp\Bitcoin\Script\Interpreter\Number $sequence * @return bool */ public function checkSequence(\BitWasp\Bitcoin\Script\Interpreter\Number $sequence) { $math = $this->adapter->getMath(); $txSequence = $this->transaction->getInput($this->nInput)->getSequence(); if ($this->transaction->getVersion() < 2) { return false; } if ($math->cmp($math->bitwiseAnd($txSequence, TransactionInputInterface::SEQUENCE_LOCKTIME_DISABLE_FLAG), 0) !== 0) { return 0; } $mask = $math->bitwiseOr(TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG, TransactionInputInterface::SEQUENCE_LOCKTIME_MASK); return $this->verifyLockTime($math->bitwiseAnd($txSequence, $mask), TransactionInputInterface::SEQUENCE_LOCKTIME_TYPE_FLAG, Number::int($math->bitwiseAnd($sequence->getInt(), $mask))); }
/** * @return bool */ public function isFullySigned() { foreach ($this->transaction->getInputs() as $i => $input) { if (array_key_exists($i, $this->inputStates)) { /** @var TxSignerContext $state */ $state = $this->inputStates[$i]; if (!$state->isFullySigned()) { return false; } } } return true; }
/** * @param UtxoView $utxoView * @param TransactionInterface $tx * @return ScriptValidationInterface */ public function queue(UtxoView $utxoView, TransactionInterface $tx) { for ($i = 0, $c = count($tx->getInputs()); $i < $c; $i++) { $output = $utxoView->fetchByInput($tx->getInput($i))->getOutput(); $witness = isset($tx->getWitnesses()[$i]) ? $tx->getWitness($i) : null; $this->results[] = $this->consensus->verify($tx, $output->getScript(), $i, $output->getValue(), $witness); } return $this; }
/** * @return Transaction */ public function getTransaction() { $transaction = $this->transaction; $inCount = count($transaction->getInputs()); for ($i = 0; $i < $inCount; $i++) { // Call regenerateScript if inputState is set, otherwise defer to previous script. try { $script = $this->getInputState($i)->regenerateScript(); } catch (BuilderNoInputState $e) { $script = $this->transaction->getInputs()->getInput($i)->getScript(); } $transaction->getInputs()->getInput($i)->setScript($script); } return $transaction; }
/** * @param TransactionInterface $transaction */ private function processTransaction(TransactionInterface $transaction) { $results = []; $nOut = count($transaction->getOutputs()); for ($i = 0; $i < $nOut; $i++) { $output = $transaction->getOutput($i); $script = $output->getScript()->getBinary(); if (!isset($results[$script])) { $results[$script] = $output->getValue(); } else { $results[$script] += $output->getValue(); } } // Compare results to known contracts foreach ($this->contracts as $contract) { $requirements = $contract['requirements']; $rCount = count($requirements); $have = 0; foreach ($requirements as $script => $value) { if (isset($results[$script])) { echo 'pmt'; if ($results[$script] >= $value) { $have++; } } } if ($have > 0) { if ($have < $rCount) { $command = 'tx.partial'; } else { $command = 'tx.complete'; } $this->listener->send(json_encode(['slug' => $contract['slug'], 'command' => $command, 'tx' => $transaction->getHex()])); } } }
/** * @param TransactionInterface $transaction * @param bool $allowExtremeFees * @return string */ public function sendrawtransaction(TransactionInterface $transaction, $allowExtremeFees = false) { $send = $this->client->execute('sendrawtransaction', [$transaction->getHex(), $allowExtremeFees]); $this->checkNotNull($send); return $send; }
/** * @param TransactionInterface $transaction * @return string */ public function serialize(TransactionInterface $transaction) { return $this->getTemplate()->write([$transaction->getVersion(), $transaction->getInputs()->getInputs(), $transaction->getOutputs()->getOutputs(), $transaction->getLockTime()]); }
/** * {@inheritdoc} * @see \BitWasp\Bitcoin\SerializableInterface::getBuffer() */ public function getBuffer() { return $this->transaction->getBuffer(); }
/** * @param TransactionInterface $transaction * @return \React\Promise\Promise */ public function transactionBroadcast(TransactionInterface $transaction) { return $this->client->request('blockchain.transaction.broadcast', [$transaction->getHex()]); }
/** * @param TransactionInterface $transaction * @return BufferInterface */ public function serialize(TransactionInterface $transaction) { $math = Bitcoin::getMath(); $int8le = new Int8($math, ByteOrder::LE); $int32le = new Int32($math, ByteOrder::LE); $uint32le = new Uint32($math, ByteOrder::LE); $varint = new VarInt($math, ByteOrder::LE); $vector = new Vector($varint, function () { }); $binary = $int32le->write($transaction->getVersion()); $flags = 0; if (!$transaction->getWitnesses()->isNull()) { $flags |= 1; } if ($flags) { $binary .= $int8le->write(0); $binary .= $int8le->write($flags); } $binary .= $vector->write($transaction->getInputs()->all()); $binary .= $vector->write($transaction->getOutputs()->all()); if ($flags & 1) { foreach ($transaction->getWitnesses() as $witness) { $binary .= $witness->getBuffer()->getBinary(); } } $binary .= $uint32le->write($transaction->getLockTime()); return new Buffer($binary); }
/** * @param TransactionInterface $transaction * @param int $outputToSpend * @param ScriptInterface|null $script * @param int $nSequence * @return $this */ public function spendOutputFrom(TransactionInterface $transaction, $outputToSpend, ScriptInterface $script = null, $nSequence = TransactionInputInterface::SEQUENCE_FINAL) { // Check TransactionOutput exists in $tx $transaction->getOutput($outputToSpend); $this->input($transaction->getTxId(), $outputToSpend, $script, $nSequence); return $this; }
/** * @param TransactionInterface $tx * @param ScriptInterface $scriptPubKey * @param int $nInputToSign * @return bool */ public function verify(TransactionInterface $tx, ScriptInterface $scriptPubKey, $nInputToSign) { $error = 0; return (bool) bitcoinconsensus_verify_script($scriptPubKey->getBinary(), $tx->getBinary(), $nInputToSign, $this->flags->getFlags(), $error); }
/** * @param TransactionInterface $transaction */ public function __construct(TransactionInterface $transaction) { $this->transaction = $transaction; $this->nInputs = count($this->transaction->getInputs()); $this->nOutputs = count($this->transaction->getOutputs()); }
/** * @param TransactionInterface $transaction * @return array */ public function convertTransactionToArray(TransactionInterface $transaction) { $inputs = []; foreach ($transaction->getInputs() as $input) { $inputs[] = $this->convertTxinToArray($input); } $outputs = []; foreach ($transaction->getOutputs() as $output) { $outputs[] = $this->convertTxoutToArray($output); } $buf = $transaction->getBuffer()->getBinary(); return ['hash' => $transaction->getTxId()->getHex(), 'version' => $transaction->getVersion(), 'inputs' => $inputs, 'outputs' => $outputs, 'locktime' => $transaction->getLockTime(), 'raw' => bin2hex($buf), 'size' => strlen($buf)]; }
/** * @param TransactionInterface $tx * @return bool */ public function isRelevantAndUpdate(TransactionInterface $tx) { $this->updateEmptyFull(); $found = false; if ($this->isFull()) { return true; } if ($this->isEmpty()) { return false; } // Check if the txid hash is in the filter $txHash = $tx->getTxId(); if ($this->containsData($txHash)) { $found = true; } // Check for relevant output scripts. We add the outpoint to the filter if found. foreach ($tx->getOutputs() as $vout => $output) { $script = $output->getScript(); $parser = $script->getScriptParser(); foreach ($parser as $exec) { if ($exec->isPush() && $this->containsData($exec->getData())) { $found = true; if ($this->isUpdateAll()) { $this->insertOutPoint($tx->makeOutPoint($vout)); } else { if ($this->isUpdatePubKeyOnly()) { $type = ScriptFactory::scriptPubKey()->classify($script); if ($type->isMultisig() || $type->isPayToPublicKey()) { $this->insertOutPoint($tx->makeOutPoint($vout)); } } } } } } if ($found) { return true; } foreach ($tx->getInputs() as $txIn) { if ($this->containsOutPoint($txIn->getOutPoint())) { return true; } $parser = $txIn->getScript()->getScriptParser(); foreach ($parser as $exec) { if ($exec->isPush() > 0 && $this->containsData($exec->getData())) { return true; } } } return false; }
/** * @param TransactionInterface $tx * @return Transaction */ public function fixTransaction(Peer $sender, TransactionInterface $tx, &$wasMalleated = false) { $c = count($tx->getInputs()); $new = new TransactionInputCollection(); for ($i = 0; $i < $c; $i++) { $input = $tx->getInput($i); $script = $input->getScript(); $classify = ScriptFactory::scriptSig()->classify($input->getScript()); $this->inputs++; if ($classify->isPayToPublicKeyHash()) { $parsed = $input->getScript()->getScriptParser()->parse(); $txSig = TransactionSignatureFactory::fromHex($parsed[0]); $txSig = $this->fixSig($sender, $txSig, $wasMalleated); $script = ScriptFactory::create()->push($txSig->getBuffer())->push($parsed[1])->getScript(); } $new->addInput(new TransactionInput($input->getTransactionId(), $input->getVout(), $script, $input->getSequence())); } return new Transaction($tx->getVersion(), $new, $tx->getOutputs(), $tx->getLockTime()); }
/** * @param UtxoView $view * @param TransactionInterface $tx * @param int $height * @param Flags $flags * @param bool|true $checkScripts * @return bool */ public function checkInputs(UtxoView $view, TransactionInterface $tx, $height, Flags $flags, $checkScripts = true) { if (!$tx->isCoinbase()) { $this->checkContextualInputs($view, $tx, $height); if ($checkScripts) { if (!$this->scriptCheck->check($view, $tx, $flags)) { throw new \RuntimeException('Script verification failed'); } } } return true; }
/** * @param TransactionInterface $tx * @param int $inputToExtract * @return $this */ public function extractSigs(TransactionInterface $tx, $inputToExtract) { $parsed = $tx->getInput($inputToExtract)->getScript()->getScriptParser()->decode(); $size = count($parsed); switch ($this->getScriptType()) { case OutputClassifier::PAYTOPUBKEYHASH: // Supply signature and public key in scriptSig if ($size === 2) { $this->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getData(), $this->ecAdapter)]; $this->publicKeys = [PublicKeyFactory::fromHex($parsed[1]->getData(), $this->ecAdapter)]; } break; case OutputClassifier::PAYTOPUBKEY: // Only has a signature in the scriptSig if ($size === 1) { $this->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getData(), $this->ecAdapter)]; } break; case OutputClassifier::MULTISIG: $redeemScript = $this->getRedeemScript(); $this->signatures = array_fill(0, count($this->publicKeys), null); if ($size > 2 && $size <= $this->scriptInfo->getKeyCount() + 2) { $sigHash = $tx->getSignatureHash(); $sigSort = new SignatureSort($this->ecAdapter); $sigs = new \SplObjectStorage(); foreach (array_slice($parsed, 1, -1) as $item) { /** @var \BitWasp\Bitcoin\Script\Parser\Operation $item */ if ($item->isPush()) { $txSig = TransactionSignatureFactory::fromHex($item->getData(), $this->ecAdapter); $hash = $sigHash->calculate($redeemScript, $inputToExtract, $txSig->getHashType()); $linked = $sigSort->link([$txSig->getSignature()], $this->publicKeys, $hash); foreach ($this->publicKeys as $key) { if ($linked->contains($key)) { $sigs[$key] = $txSig; } } } } // We have all the signatures from the input now. array_shift the sigs for a public key, as it's encountered. foreach ($this->publicKeys as $idx => $key) { $this->signatures[$idx] = isset($sigs[$key]) ? $sigs[$key] : null; } } break; } return $this; }
/** * @param TransactionInterface $tx * @param ScriptInterface $scriptPubKey * @param int $nInputToSign * @return bool */ public function verify(TransactionInterface $tx, ScriptInterface $scriptPubKey, $nInputToSign) { $inputs = $tx->getInputs(); $interpreter = new Interpreter($this->adapter, $tx, $this->flags); return $interpreter->verify($inputs[$nInputToSign]->getScript(), $scriptPubKey, $nInputToSign); }