protected function addressFromScriptHex($script_hex) { $address = null; try { $script = ScriptFactory::fromHex($script_hex); $classifier = new InputClassifier($script); if ($classifier->isPayToPublicKeyHash()) { $decoded = $script->getScriptParser()->decode(); $public_key = PublicKeyFactory::fromHex($decoded[1]->getData()); $address = $public_key->getAddress()->getAddress(); } else { if ($classifier->isPayToScriptHash()) { $decoded = $script->getScriptParser()->decode(); $hex_buffer = $decoded[count($decoded) - 1]->getData(); $sh_address = new ScriptHashAddress(ScriptFactory::fromHex($hex_buffer)->getScriptHash()); $address = $sh_address->getAddress(); } else { // unknown script type Log::debug("Unable to classify script " . substr($hex, 0, 20) . "..."); } } } catch (Exception $e) { Log::error("failed to get address from script. " . $e->getMessage()); } return $address; }
/** * @param ScriptInterface $script */ public function __construct(ScriptInterface $script) { $publicKeys = []; $parse = $script->getScriptParser()->decode(); if (count($parse) < 4) { throw new \InvalidArgumentException('Malformed multisig script'); } $mCode = $parse[0]->getOp(); $nCode = $parse[count($parse) - 2]->getOp(); $this->m = (int) \BitWasp\Bitcoin\Script\decodeOpN($mCode); foreach (array_slice($parse, 1, -2) as $key) { /** @var \BitWasp\Bitcoin\Script\Parser\Operation $key */ if (!$key->isPush()) { throw new \RuntimeException('Malformed multisig script'); } $publicKeys[] = PublicKeyFactory::fromHex($key->getData()); } $n = \BitWasp\Bitcoin\Script\decodeOpN($nCode); $this->n = count($publicKeys); if ($this->n === 0 || $this->n !== $n) { throw new \LogicException('No public keys found in script'); } $this->script = $script; $this->keys = $publicKeys; }
/** * @param ScriptInterface $script */ public function __construct(ScriptInterface $script) { $this->script = $script; $chunks = $script->getScriptParser()->decode(); if (count($chunks) < 1 || !$chunks[0]->isPush()) { throw new \InvalidArgumentException('Malformed pay-to-pubkey script'); } $this->publicKey = PublicKeyFactory::fromHex($chunks[0]->getData()); }
/** * @param Parser $parser * @return HierarchicalKey * @throws ParserOutOfRange */ public function fromParser(Parser &$parser) { try { list($bytes, $depth, $parentFingerprint, $sequence, $chainCode, $keyData) = $this->getTemplate()->parse($parser); $bytes = $bytes->getHex(); } catch (ParserOutOfRange $e) { throw new ParserOutOfRange('Failed to extract HierarchicalKey from parser'); } if ($bytes !== $this->network->getHDPubByte() && $bytes !== $this->network->getHDPrivByte()) { throw new \InvalidArgumentException("HD key magic bytes do not match network magic bytes"); } $key = $this->network->getHDPrivByte() == $bytes ? PrivateKeyFactory::fromHex($keyData->slice(1)->getHex(), true, $this->ecAdapter) : PublicKeyFactory::fromHex($keyData->getHex(), $this->ecAdapter); return new HierarchicalKey($this->ecAdapter, $depth, $parentFingerprint, $sequence, $chainCode, $key); }
/** * @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 $script * @return RedeemScript */ public static function fromScript(ScriptInterface $script) { $publicKeys = []; $parse = $script->getScriptParser()->parse(); $opCodes = $script->getOpcodes(); $m = $opCodes->getOpByName($parse[0]) - $opCodes->getOpByName('OP_1') + 1; foreach (array_slice($parse, 1, -2) as $item) { if (!$item instanceof Buffer) { throw new \RuntimeException('Unable to load public key'); } $publicKeys[] = PublicKeyFactory::fromHex($item->getHex()); } if (count($publicKeys) == 0) { throw new \LogicException('No public keys found in script'); } return new self($m, $publicKeys); }
/** * @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 ScriptInterface $script * @param BufferInterface $sigBuf * @param BufferInterface $keyBuf * @param int $sigVersion * @param int $flags * @return bool * @throws ScriptRuntimeException */ public function checkSig(ScriptInterface $script, BufferInterface $sigBuf, BufferInterface $keyBuf, $sigVersion, $flags) { $this->checkSignatureEncoding($sigBuf, $flags)->checkPublicKeyEncoding($keyBuf, $flags); try { $txSignature = TransactionSignatureFactory::fromHex($sigBuf->getHex()); $publicKey = PublicKeyFactory::fromHex($keyBuf->getHex()); if ($sigVersion === 1) { $hasher = new V1Hasher($this->transaction, $this->amount); } else { $hasher = new Hasher($this->transaction); } $hash = $hasher->calculate($script, $this->nInput, $txSignature->getHashType()); return $this->adapter->verify($hash, $publicKey, $txSignature->getSignature()); } catch (\Exception $e) { return false; } }
/** * @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()); return $this->ecAdapter->verify($this->transaction->getSignatureHash()->calculate($script, $this->inputToSign, $txSignature->getHashType()), $publicKey, $txSignature->getSignature()); } catch (\Exception $e) { return false; } }
/** * @param PrivateKeyInterface $privateKey * @return \BitWasp\Bitcoin\Key\PublicKey * @throws \Exception */ public function privateToPublic(PrivateKeyInterface $privateKey) { $publicKey = ''; $ret = \secp256k1_ec_pubkey_create($privateKey->getBuffer()->getBinary(), (int) $privateKey->isCompressed(), $publicKey); if ($ret === 1) { $public = PublicKeyFactory::fromHex(bin2hex($publicKey), $this); return $public; } throw new \Exception('Unable to convert private to public key'); }
/** * The function only returns true when $scriptPubKey could be classified * * @param PrivateKeyInterface $key * @param ScriptInterface $scriptPubKey * @param string $outputType * @param BufferInterface[] $results * @param int $sigVersion * @return bool */ private function doSignature(PrivateKeyInterface $key, ScriptInterface $scriptPubKey, &$outputType, array &$results, $sigVersion = 0) { $return = []; $outputType = (new OutputClassifier($scriptPubKey))->classify($return); if ($outputType === OutputClassifier::UNKNOWN) { throw new \RuntimeException('Cannot sign unknown script type'); } if ($outputType === OutputClassifier::PAYTOPUBKEY) { $publicKeyBuffer = $return; $results[] = $publicKeyBuffer; $this->requiredSigs = 1; $publicKey = PublicKeyFactory::fromHex($publicKeyBuffer); if ($publicKey->getBinary() === $key->getPublicKey()->getBinary()) { $this->signatures[0] = $this->calculateSignature($key, $scriptPubKey, $sigVersion); } return true; } if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) { /** @var BufferInterface $pubKeyHash */ $pubKeyHash = $return; $results[] = $pubKeyHash; $this->requiredSigs = 1; if ($pubKeyHash->getBinary() === $key->getPublicKey()->getPubKeyHash()->getBinary()) { $this->signatures[0] = $this->calculateSignature($key, $scriptPubKey, $sigVersion); $this->publicKeys[0] = $key->getPublicKey(); } return true; } if ($outputType === OutputClassifier::MULTISIG) { $info = new Multisig($scriptPubKey); foreach ($info->getKeys() as $publicKey) { $results[] = $publicKey->getBuffer(); } $this->publicKeys = $info->getKeys(); $this->requiredSigs = $info->getKeyCount(); foreach ($this->publicKeys as $keyIdx => $publicKey) { if ($publicKey->getBinary() == $key->getPublicKey()->getBinary()) { $this->signatures[$keyIdx] = $this->calculateSignature($key, $scriptPubKey, $sigVersion); } } return true; } if ($outputType === OutputClassifier::PAYTOSCRIPTHASH) { /** @var BufferInterface $scriptHash */ $scriptHash = $return; $results[] = $scriptHash; return true; } if ($outputType === OutputClassifier::WITNESS_V0_KEYHASH) { /** @var BufferInterface $pubKeyHash */ $pubKeyHash = $return; $results[] = $pubKeyHash; $this->requiredSigs = 1; if ($pubKeyHash->getBinary() === $key->getPublicKey()->getPubKeyHash()->getBinary()) { $script = ScriptFactory::sequence([Opcodes::OP_DUP, Opcodes::OP_HASH160, $pubKeyHash, Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG]); $this->signatures[0] = $this->calculateSignature($key, $script, 1); $this->publicKeys[0] = $key->getPublicKey(); } return true; } if ($outputType === OutputClassifier::WITNESS_V0_SCRIPTHASH) { /** @var BufferInterface $scriptHash */ $scriptHash = $return; $results[] = $scriptHash; return true; } return false; }
/** * @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; }