/** * @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 int $sighashType * @return Buffer|BufferInterface */ public function hashSequences($sighashType) { if (!($sighashType & SigHash::ANYONECANPAY) && ($sighashType & 0x1f) != SigHash::SINGLE && ($sighashType & 0x1f) != SigHash::NONE) { $binary = ''; foreach ($this->transaction->getInputs() as $input) { $binary .= Buffer::int($input->getSequence())->flip()->getBinary(); } return Hash::sha256d(new Buffer($binary)); } return new Buffer('', 32); }
/** * @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); }
/** * @param TransactionInterface $tx */ private function deleteSpends(TransactionInterface $tx) { foreach ($tx->getInputs()->getInputs() as $v => $input) { if (!$input->isCoinBase()) { $this->delete($input->getTransactionId(), $input->getVout()); } } }
/** * @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; }
/** * @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; }
/** * @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; }
/** * @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 * @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 array $array * @return $this */ private function replace(array $array = []) { $this->transaction = new Transaction(array_key_exists('version', $array) ? $array['version'] : $this->transaction->getVersion(), array_key_exists('inputs', $array) ? $array['inputs'] : $this->transaction->getInputs(), array_key_exists('outputs', $array) ? $array['outputs'] : $this->transaction->getOutputs(), array_key_exists('witness', $array) ? $array['witness'] : $this->transaction->getWitnesses(), array_key_exists('nLockTime', $array) ? $array['nLockTime'] : $this->transaction->getLockTime()); return $this; }
/** * @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 UtxoView $view * @param TransactionInterface $tx * @param int $spendHeight * @return $this */ public function checkContextualInputs(UtxoView $view, TransactionInterface $tx, $spendHeight) { $valueIn = gmp_init(0); for ($i = 0, $nInputs = count($tx->getInputs()); $i < $nInputs; $i++) { /*if ($out->isCoinbase()) { // todo: cb / height if ($spendHeight - $out->getHeight() < $this->params->coinbaseMaturityAge()) { return false; } }*/ $value = gmp_init($view->fetchByInput($tx->getInput($i))->getOutput()->getValue(), 10); $valueIn = $this->math->add($valueIn, $value); $this->consensus->checkAmount($valueIn); } $valueOut = gmp_init(0); foreach ($tx->getOutputs() as $output) { $valueOut = $this->math->add($valueOut, gmp_init($output->getValue(), 10)); $this->consensus->checkAmount($valueOut); } if ($this->math->cmp($valueIn, $valueOut) < 0) { throw new \RuntimeException('Value-in is less than value-out'); } $fee = $this->math->sub($valueIn, $valueOut); $this->consensus->checkAmount($fee); return $this; }
/** * @param TransactionInterface $tx * @param $inputToExtract * @return $this */ public function extractSigs(TransactionInterface $tx, $inputToExtract) { $inputs = $tx->getInputs(); $parsed = $inputs[$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(); $keys = $this->scriptInfo->getKeys(); foreach ($keys as $idx => $key) { $this->setSignature($idx, null); } if ($size > 2 && $size <= $this->scriptInfo->getKeyCount() + 2) { $sigs = []; foreach ($keys as $key) { $sigs[$key->getPubKeyHash()->getHex()] = []; } // Extract Signatures (as buffers), then compile arrays of [pubkeyHash => signature] $sigHash = new Hasher($tx); 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); $linked = $this->ecAdapter->associateSigs([$txSig->getSignature()], $sigHash->calculate($redeemScript, $inputToExtract, $txSig->getHashType()), $keys); if (count($linked)) { $key = array_keys($linked)[0]; $sigs[$key] = array_merge($sigs[$key], [$txSig]); } } } // We have all the signatures from the tx now. array_shift the sigs for a public key, as it's encountered. foreach ($keys as $idx => $key) { $hash = $key->getPubKeyHash()->getHex(); $this->setSignature($idx, isset($sigs[$hash]) ? array_shift($sigs[$hash]) : null); } } break; } return $this; }
/** * @param UtxoView $view * @param TransactionInterface $tx * @param $spendHeight * @return bool */ public function checkContextualInputs(UtxoView $view, TransactionInterface $tx, $spendHeight) { $valueIn = 0; for ($i = 0; $i < count($tx->getInputs()); $i++) { $utxo = $view->fetchByInput($tx->getInput($i)); /*if ($out->isCoinbase()) { // todo: cb / height if ($spendHeight - $out->getHeight() < $this->params->coinbaseMaturityAge()) { return false; } }*/ $value = $utxo->getOutput()->getValue(); $valueIn = $this->math->add($value, $valueIn); if (!$this->consensus->checkAmount($valueIn) || !$this->consensus->checkAmount($value)) { throw new \RuntimeException('CheckAmount failed for inputs value'); } } $valueOut = 0; foreach ($tx->getOutputs() as $output) { $valueOut = $this->math->add($output->getValue(), $valueOut); if (!$this->consensus->checkAmount($valueOut) || !$this->consensus->checkAmount($output->getValue())) { throw new \RuntimeException('CheckAmount failed for outputs value'); } } if ($this->math->cmp($valueIn, $valueOut) < 0) { throw new \RuntimeException('Value-in is less than value out'); } $fee = $this->math->sub($valueIn, $valueOut); if ($this->math->cmp($fee, 0) < 0) { throw new \RuntimeException('Fee is less than zero'); } if (!$this->consensus->checkAmount($fee)) { throw new \RuntimeException('CheckAmount failed for fee'); } return true; }
/** * @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 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 $transaction * @return string */ public function serialize(TransactionInterface $transaction) { return $this->getTemplate()->write([$transaction->getVersion(), $transaction->getInputs()->getInputs(), $transaction->getOutputs()->getOutputs(), $transaction->getLockTime()]); }
/** * @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 $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); }
/** * @param TransactionInterface $tx * @param ScriptInterface $scriptPubKey * @param int $nInputToSign * @param int $amount * @param ScriptWitnessInterface|null $witness * @return bool */ public function verify(TransactionInterface $tx, ScriptInterface $scriptPubKey, $nInputToSign, $amount, ScriptWitnessInterface $witness = null) { $inputs = $tx->getInputs(); $interpreter = new Interpreter($this->adapter); return $interpreter->verify($inputs[$nInputToSign]->getScript(), $scriptPubKey, $this->flags, new Checker($this->adapter, $tx, $nInputToSign, $amount), $witness); }