/** * Composes a send transaction * @param string $asset A counterparty asset name or BTC * @param mixed $quantity Quantity of asset to send. Accepts a float or a Tokenly\CounterpartyTransactionComposer\Quantity or Tokenly\CryptoQuantity\CryptoQuantity object. Use a Quantity object for indivisible assets. * @param mixed $destination A single destination bitcoin address. For BTC sends an array of [[address, amount], [address, amount]] is also allowed. Amounts should be float values. * @param string $private_key_wif The private key in ASCII WIF format. This can be null to compose an unsigned transaction. * @param array $utxos An array of UTXOs. Each UTXO should be ['txid' => txid, 'n' => n, 'amount' => amount (in satoshis), 'script' => script hexadecimal string] * @param mixed $change_address_collection a single address string to receive all change. Or an array of [[address, amount], [address, amount], [address]]. Amounts should be float values. An address with no amount for the last entry will send the remaining change to that address. * @param float $fee A fee * @param float $btc_dust Amount of BTC dust to send with the Counterparty transaction. * @return Array returns a ComposedTransaction object */ public function composeSend($asset, $quantity, $destination, $private_key_wif, $utxos, $change_address_collection = null, $fee = null, $btc_dust = null) { if ($asset == 'BTC') { return $this->composeBTCSend($quantity, $destination, $private_key_wif, $utxos, $change_address_collection, $fee); } $fee_satoshis = $fee === null ? self::DEFAULT_FEE : intval(round($fee * self::SATOSHI)); $btc_dust_satoshis = $btc_dust === null ? self::DEFAULT_BTC_DUST : intval(round($btc_dust * self::SATOSHI)); // get total and change amount $change_amounts = $this->calculateAndValidateChange($utxos, $btc_dust_satoshis, $fee_satoshis, $change_address_collection); $tx_builder = TransactionFactory::build(); // add the UTXO inputs $transaction_outputs = $this->addInputsAndReturnPreviousOutputs($utxos, $tx_builder); // pay the btc_dust to the destination if (is_array($destination)) { throw new Exception("Multiple destinations are not supported for counterparty sends", 1); } $tx_builder->payToAddress($btc_dust_satoshis, AddressFactory::fromString($destination)); // build the OP_RETURN script $op_return_builder = new OpReturnBuilder(); $op_return = $op_return_builder->buildOpReturn($quantity, $asset, $utxos[0]['txid']); $script = ScriptFactory::create()->op('OP_RETURN')->push(Buffer::hex($op_return, 28))->getScript(); $tx_builder->output(0, $script); // pay the change to self $this->payChange($change_amounts, $tx_builder); // sign if ($private_key_wif !== null) { $signed_transaction = $this->signTx($private_key_wif, $tx_builder, $transaction_outputs); return $this->buildReturnValuesFromTransactionAndInputs($signed_transaction, $utxos, true); } return $this->buildReturnValuesFromTransactionAndInputs($tx_builder->get(), $utxos, false); }
/** * @param int $m * @param PublicKeyInterface[] $keys * @param bool|true $sort * @return ScriptCreator|Script */ public function multisig($m, array $keys = [], $sort = true) { $n = count($keys); if ($m > $n) { throw new \LogicException('Required number of sigs exceeds number of public keys'); } if ($n > 16) { throw new \LogicException('Number of public keys is greater than 16'); } if ($sort) { $keys = Buffertools::sort($keys); } $opM = \BitWasp\Bitcoin\Script\encodeOpN($m); $opN = \BitWasp\Bitcoin\Script\encodeOpN($n); $script = ScriptFactory::create(); foreach ($keys as $key) { if (!$key instanceof PublicKeyInterface) { throw new \LogicException('Values in $keys[] must be a PublicKey'); } $script->push($key->getBuffer()); } $keyBuf = $script->getScript()->getBuffer(); $script = new Script(new Buffer(chr($opM) . $keyBuf->getBinary() . chr($opN) . chr(Opcodes::OP_CHECKMULTISIG))); return $script; }
/** * @param ScriptInterface $inputScript * @param ScriptInterface $redeemScript * @return ScriptInterface */ public function payToScriptHash(ScriptInterface $inputScript, ScriptInterface $redeemScript) { return ScriptFactory::create($inputScript->getBuffer())->push($redeemScript->getBuffer())->getScript(); }
/** * @return ScriptInterface */ public function getScript() { return ScriptFactory::create()->int($this->version)->push($this->program)->getScript(); }
/** * @param Buffer $secret * @param PublicKeyInterface $a1 * @param PublicKeyInterface $a2 * @param PublicKeyInterface $b1 * @param PublicKeyInterface $b2 * @return ScriptInterface */ public function payToLightningChannel(Buffer $secret, PublicKeyInterface $a1, PublicKeyInterface $a2, PublicKeyInterface $b1, PublicKeyInterface $b2) { return ScriptFactory::create()->op('OP_DEPTH')->op('OP_3')->op('OP_EQUAL')->op('OP_IF')->op('OP_HASH160')->push(Hash::sha256ripe160($secret))->op('OP_EQUALVERIFY')->concat(ScriptFactory::scriptPubKey()->multisig(2, [$a1, $b1]))->op('OP_ELSE')->concat(ScriptFactory::scriptPubKey()->multisig(2, [$a2, $b2]))->op('OP_ENDIF')->getScript(); }
/** * @return \BitWasp\Bitcoin\Block\BlockInterface */ public function getGenesisBlock() { $timestamp = new Buffer('The Times 03/Jan/2009 Chancellor on brink of second bailout for banks', null, $this->math); $publicKey = Buffer::hex('04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f', null, $this->math); $inputScript = ScriptFactory::create()->push(Buffer::int('486604799', 4, $this->math)->flip())->push(Buffer::int('4', null, $this->math))->push($timestamp)->getScript(); $outputScript = ScriptFactory::create()->push($publicKey)->op('OP_CHECKSIG')->getScript(); return new Block($this->math, $this->getGenesisBlockHeader(), new TransactionCollection([(new TxBuilder())->version('1')->input(new Buffer('', 32), 0xffffffff, $inputScript)->output(5000000000, $outputScript)->locktime(0)->get()])); }
/** * @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()); }
/** * 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 OutputBuf $outBuf * @return TransactionOutput */ private function bufToOutput(OutputBuf $outBuf) { $script = ScriptFactory::create(new Buffer($outBuf->getScript())); $output = new TransactionOutput($outBuf->getAmount(), $script); return $output; }
/** * @param array $signatures * @param array $publicKeys * @return ScriptInterface */ public function makeScriptSig(array $signatures = [], array $publicKeys = []) { $inputScript = $this->handler->makeScriptSig($signatures, $publicKeys); return ScriptFactory::create($inputScript->getBuffer())->push($this->redeemScript->getBuffer())->getScript(); }
/** * @return SigValues */ public function serializeSignatures() { static $emptyScript = null; static $emptyWitness = null; if (is_null($emptyScript) || is_null($emptyWitness)) { $emptyScript = new Script(); $emptyWitness = new ScriptWitness([]); } /** @var BufferInterface[] $return */ $outputType = (new OutputClassifier($this->txOut->getScript()))->classify(); /** @var SigValues $answer */ $answer = new SigValues($emptyScript, $emptyWitness); $serialized = $this->serializeSimpleSig($outputType, $answer); $p2sh = false; if (!$serialized && $outputType === OutputClassifier::PAYTOSCRIPTHASH) { $p2sh = true; $outputType = (new OutputClassifier($this->redeemScript))->classify(); $serialized = $this->serializeSimpleSig($outputType, $answer); } if (!$serialized && $outputType === OutputClassifier::WITNESS_V0_KEYHASH) { $answer = new SigValues($emptyScript, new ScriptWitness([$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()])); } else { if (!$serialized && $outputType === OutputClassifier::WITNESS_V0_SCRIPTHASH) { $outputType = (new OutputClassifier($this->witnessScript))->classify(); $serialized = $this->serializeSimpleSig($outputType, $answer); if ($serialized) { $data = []; foreach ($answer->getScriptSig()->getScriptParser()->decode() as $o) { $data[] = $o->getData(); } $data[] = $this->witnessScript->getBuffer(); $answer = new SigValues($emptyScript, new ScriptWitness($data)); } } } if ($p2sh) { $answer = new SigValues(ScriptFactory::create($answer->getScriptSig()->getBuffer())->push($this->redeemScript->getBuffer())->getScript(), $answer->getScriptWitness()); } return $answer; }
<?php require_once "../vendor/autoload.php"; use BitWasp\Bitcoin\Script\ScriptFactory; use BitWasp\Bitcoin\Transaction\Transaction; $ec = \BitWasp\Bitcoin\Bitcoin::getEcAdapter(); $script = ScriptFactory::create()->op('OP_1')->op('OP_1')->op('OP_ADD')->op('OP_2')->op('OP_EQUALVERIFY'); echo "Formed script: " . $script->getHex() . "\n"; print_r($script->getScriptParser()->parse()); $factory = new \BitWasp\Bitcoin\Script\Interpreter\InterpreterFactory($ec); $flags = $factory->flags(0); $i = $factory->getNativeInterpreter(new Transaction(), $flags); $result = $i->setScript($script)->run(); echo "Script result: " . ($result ? 'true' : 'false') . "\n";
/** * @return \BitWasp\Bitcoin\Script\Script */ public function regenerateScript() { // todo: this is worrisome, should have some way to fail and defer to the original script $signatures = array_filter($this->getSignatures()); $script = $this->execForInputTypes(function () use(&$signatures) { return count($signatures) == 1 ? ScriptFactory::scriptSig()->payToPubKeyHash($signatures[0], $this->publicKeys[0]) : ScriptFactory::create(); }, function () use(&$signatures) { return count($signatures) == 1 ? ScriptFactory::scriptSig()->payToPubKey($signatures[0]) : ScriptFactory::create(); }, function () use(&$signatures) { return count($signatures) > 0 ? ScriptFactory::scriptSig()->multisigP2sh($this->getRedeemScript(), array_filter($this->signatures)) : ScriptFactory::create(); }); return $script; }
<?php require_once "../vendor/autoload.php"; use BitWasp\Bitcoin\Script\ScriptFactory; use BitWasp\Bitcoin\Transaction\Transaction; use BitWasp\Bitcoin\Script\Script; $ec = \BitWasp\Bitcoin\Bitcoin::getEcAdapter(); $scriptSig = ScriptFactory::create()->int(1)->int(1)->getScript(); $scriptPubKey = ScriptFactory::create()->op('OP_ADD')->int(2)->op('OP_EQUAL')->getScript(); echo "Formed script: " . $scriptSig->getHex() . " " . $scriptPubKey->getHex() . "\n"; $flags = new \BitWasp\Bitcoin\Flags(0); $i = new \BitWasp\Bitcoin\Script\Interpreter\Interpreter($ec, new Transaction(), $flags); $result = $i->verify($scriptSig, $scriptPubKey, 0); echo "Script result: " . ($result ? 'true' : 'false') . "\n";
<?php require "vendor/autoload.php"; use BitWasp\Bitcoin\Script\ScriptFactory; use BitWasp\Bitcoin\Transaction\TransactionFactory; use BitWasp\Buffertools\Buffer; $scriptPubKey = ScriptFactory::create()->op('OP_1')->op('OP_EQUAL')->getScript(); $scriptSig = ScriptFactory::create()->op('OP_1')->getScript(); $tx = TransactionFactory::build()->input(Buffer::hex('0000000000000000000000000000000000000000000000000000000000000001'), 0, $scriptSig)->output(1, new \BitWasp\Bitcoin\Script\Script())->get(); $t1 = ['txid' => 'a', 'tx' => $tx->getHex(), 'scripts' => [$scriptPubKey->getHex()]]; $msg = ['id' => 'asdfasdfsadf', 'flags' => 0, 'txs' => [$t1]]; echo 'sending'; $context = new \ZMQContext(); $push = $context->getSocket(\ZMQ::SOCKET_REQ); $push->connect('tcp://127.0.0.1:6661'); $push->send(json_encode($msg)); $response = $push->recv(); echo $response . PHP_EOL;
/** * @param TransactionSignatureInterface $signature * @return Script */ public function payToPubKey(TransactionSignatureInterface $signature) { return ScriptFactory::create()->push($signature->getBuffer()); }
/** * @param ScriptInterface $scriptSig * @param ScriptInterface $scriptPubKey * @param int $flags * @param Checker $checker * @param ScriptWitnessInterface|null $witness * @return bool */ public function verify(ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, $flags, Checker $checker, ScriptWitnessInterface $witness = null) { static $emptyWitness = null; if ($emptyWitness === null) { $emptyWitness = new ScriptWitness([]); } $witness = $witness ?: $emptyWitness; if (($flags & self::VERIFY_SIGPUSHONLY) != 0 && !$scriptSig->isPushOnly()) { return false; } $stack = new Stack(); if (!$this->evaluate($scriptSig, $stack, 0, $flags, $checker)) { return false; } $backup = []; if ($flags & self::VERIFY_P2SH) { foreach ($stack as $s) { $backup[] = $s; } } if (!$this->evaluate($scriptPubKey, $stack, 0, $flags, $checker)) { return false; } if ($stack->isEmpty()) { return false; } if (false === $this->castToBool($stack[-1])) { return false; } $program = null; if ($flags & self::VERIFY_WITNESS) { if ($scriptPubKey->isWitness($program)) { /** @var WitnessProgram $program */ if ($scriptSig->getBuffer()->getSize() !== 0) { return false; } if (!$this->verifyWitnessProgram($program, $witness, $flags, $checker)) { return false; } $stack->resize(1); } } if ($flags & self::VERIFY_P2SH && (new OutputClassifier($scriptPubKey))->isPayToScriptHash()) { if (!$scriptSig->isPushOnly()) { return false; } $stack = new Stack(); foreach ($backup as $i) { $stack->push($i); } // Restore mainStack to how it was after evaluating scriptSig if ($stack->isEmpty()) { return false; } // Load redeemscript as the scriptPubKey $scriptPubKey = new Script($stack->bottom()); $stack->pop(); if (!$this->evaluate($scriptPubKey, $stack, 0, $flags, $checker)) { return false; } if ($stack->isEmpty()) { return false; } if (!$this->castToBool($stack->bottom())) { return false; } if ($flags & self::VERIFY_WITNESS) { if ($scriptPubKey->isWitness($program)) { /** @var WitnessProgram $program */ if ($scriptSig != ScriptFactory::create()->push($scriptPubKey->getBuffer())->getScript()) { return false; // SCRIPT_ERR_WITNESS_MALLEATED_P2SH } if (!$this->verifyWitnessProgram($program, $witness, $flags, $checker)) { return false; } $stack->resize(1); } } } if ($flags & self::VERIFY_CLEAN_STACK != 0) { if (!($flags & self::VERIFY_P2SH != 0 && $flags & self::VERIFY_WITNESS != 0)) { return false; // implied flags required } if (count($stack) != 1) { return false; // Cleanstack } } if ($flags & self::VERIFY_WITNESS) { if (!$flags & self::VERIFY_P2SH) { return false; // } if ($program === null && !$witness->isNull()) { return false; // SCRIPT_ERR_WITNESS_UNEXPECTED } } return true; }