/** * @param EcAdapterInterface $ecAdapter * @param ScriptInterface $outputScript * @param RedeemScript $redeemScript */ public function __construct(EcAdapterInterface $ecAdapter, ScriptInterface $outputScript, RedeemScript $redeemScript = null) { $classifier = new OutputClassifier($outputScript); $this->scriptType = $this->prevOutType = $classifier->classify(); // Reclassify if the output is P2SH, so we know how to sign it. if ($this->scriptType == OutputClassifier::PAYTOSCRIPTHASH) { if (null === $redeemScript) { throw new \InvalidArgumentException('Redeem script is required when output is P2SH'); } $rsClassifier = new OutputClassifier($redeemScript); $this->scriptType = $rsClassifier->classify(); } // Gather public keys from redeemScript / outputScript $this->ecAdapter = $ecAdapter; $this->redeemScript = $redeemScript; $this->prevOutScript = $outputScript; // According to scriptType, extract public keys $this->execForInputTypes(function () { // For pay to pub key hash - nothing useful in output script $this->publicKeys = []; }, function () { // For pay to pub key - we can extract this from the output script $chunks = $this->prevOutScript->getScriptParser()->parse(); $this->publicKeys = [PublicKeyFactory::fromHex($chunks[0]->getHex(), $this->ecAdapter)]; }, function () { // Multisig - refer to the redeemScript $this->publicKeys = $this->redeemScript->getKeys(); }); }
/** * @return bool */ public function isPayToScriptHash() { if (count($this->evalScript) == 0) { return false; } $final = end($this->evalScript); if (!$final || !$final instanceof Buffer) { return false; } $type = new OutputClassifier(new Script($final)); return false === in_array($type->classify(), [self::UNKNOWN, self::PAYTOSCRIPTHASH]); }
/** * @return bool */ public function isPayToScriptHash() { if (count($this->decoded) < 1) { return false; } $final = end($this->decoded); if (!$final || !$final->isPush()) { return false; } $type = new OutputClassifier(new Script($final->getData())); return false === in_array($type->classify(), [self::UNKNOWN, self::PAYTOSCRIPTHASH], true); }
/** * @param ScriptInterface $script * @param NetworkInterface $network * @return String * @throws \RuntimeException */ public static function getAssociatedAddress(ScriptInterface $script, NetworkInterface $network = null) { $classifier = new OutputClassifier($script); $network = $network ?: Bitcoin::getNetwork(); try { if ($classifier->isPayToPublicKey()) { $address = PublicKeyFactory::fromHex($script->getScriptParser()->decode()[0]->getData())->getAddress(); } else { $address = self::fromOutputScript($script); } return Base58::encodeCheck(Buffer::hex($network->getAddressByte() . $address->getHash(), 21)); } catch (\Exception $e) { throw new \RuntimeException('No address associated with this script type'); } }
/** * @param ScriptInterface $scriptSig * @param ScriptInterface $scriptPubKey * @param $nInputToSign * @return bool * @throws \Exception */ public function verify(ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, $nInputToSign) { $this->inputToSign = $nInputToSign; if (!$this->setScript($scriptSig)->run()) { return false; } $mainStack = $this->state->getMainStack(); $stackCopy = new ScriptStack(); if ($this->flags->checkFlags(InterpreterInterface::VERIFY_P2SH)) { $stackCopy = $this->state->cloneMainStack(); } if (!$this->setScript($scriptPubKey)->run()) { return false; } if ($mainStack->size() == 0) { return false; } if (false === $this->castToBool($mainStack->top(-1))) { return false; } $verifier = new OutputClassifier($scriptPubKey); if ($this->flags->checkFlags(InterpreterInterface::VERIFY_P2SH) && $verifier->isPayToScriptHash()) { if (!$scriptSig->isPushOnly()) { return false; } // Restore mainStack to how it was after evaluating scriptSig $mainStack = $this->state->restoreMainStack($stackCopy)->getMainStack(); if ($mainStack->size() == 0) { return false; } // Load redeemscript as the scriptPubKey $scriptPubKey = new Script($mainStack->top(-1)); $mainStack->pop(); if (!$this->setScript($scriptPubKey)->run()) { return false; } } return true; }