/** * @param integer $input * @return TxSignerContext * @throws BuilderNoInputState */ public function inputState($input) { $this->transaction->getInput($input); if (!array_key_exists($input, $this->inputStates)) { throw new BuilderNoInputState('State not found for this input'); } return $this->inputStates[$input]; }
/** * @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 $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 \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))); }
/** * @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 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 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; }