Bitcoin transaction forwarding
Forwarding of individual Bitcoin transactions to one or more addresses is a new feature in Coin Tools.
Because it spends the outputs from the transaction that is being forwarded instead of making a regular payment from the wallet pool of unspent outputs it is not necessary to wait for confirmations before sending the new transaction. If the original transaction did not ultimately make it onto the blockchain then the forwarding transaction would also not make it onto the blockchain.
However, it is prudent to wait for 1 confirmation due to a problem called transaction malleability. When a transaction is broadcast to the Bitcoin network it has a txid to uniquely identify it. However, a mischievous actor could take the transaction and change something that wouldn't affect the digital signature but would change the txid. If this altered transaction made it onto the blockchain instead of the original version, any subsequent transactions that were referring to the original txid would be invalid.
If this were to occur it wouldn't cause money loss and it could be overcome my detecting it and then reissuing the subsequent transactions. A cleaner solution is to wait for one confirmation before spending the outputs. Once the transaction is on the blockchain it is extremely unlikely that the block would be replaced by one with an altered version of the transaction.
Transaction malleability is due to a bug in the design of Bitcoin. While it cannot now be solved completely, changes to the Bitcoin software have been made to lessen the impact of this problem.
The \Drupal\cointools_daemon\Client::forwardTransaction() method in Coin Tools allows for transactions to be forwarded to multiple addresses. Output addresses have quantities attached to them to define what ratio of the input amount is sent to each address. This is similar to how Coinsplit operates, although they wait for 3 confirmations and may do a general wallet spend instead of forwarding the specific transaction.
<span style="color: #000000"><span style="color: #0000BB"><?php<br> </span><span style="color: #FF8000">/**<br> * Forwards a bitcoin transaction to one or more addresses with defined<br> * proportionality.<br> *<br> * @param $txid<br> * txid of transaction to forward.<br> * @param array $addresses<br> * List of addresses that can be spent from.<br> * @param array $outputs<br> * Where to forward the bitcoin to.<br> * Keys are destinations addresses.<br> * Values are proportional quantities.<br> * @param bool $tx_confirm_target<br> * The fee should be calculated for the transaction to reach the blockchain<br> * after this many blocks. default = 1<br> *<br> * @return string<br> * txid of the forwarding transaction.<br> */<br> </span><span style="color: #007700">public function </span><span style="color: #0000BB">forwardTransaction</span><span style="color: #007700">(</span><span style="color: #0000BB">$txid</span><span style="color: #007700">, array </span><span style="color: #0000BB">$addresses</span><span style="color: #007700">, array </span><span style="color: #0000BB">$outputs</span><span style="color: #007700">, </span><span style="color: #0000BB">$tx_confirm_target </span><span style="color: #007700">= </span><span style="color: #0000BB">1</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$transaction_in </span><span style="color: #007700">= </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">transactionLoad</span><span style="color: #007700">(</span><span style="color: #0000BB">$txid</span><span style="color: #007700">);<br> </span><span style="color: #FF8000">// Find inputs and amount for new transaction.<br> </span><span style="color: #0000BB">$inputs </span><span style="color: #007700">= [];<br> </span><span style="color: #0000BB">$transaction_amount </span><span style="color: #007700">= </span><span style="color: #0000BB">0</span><span style="color: #007700">;<br> foreach (</span><span style="color: #0000BB">$transaction_in</span><span style="color: #007700">[</span><span style="color: #DD0000">'vout'</span><span style="color: #007700">] as </span><span style="color: #0000BB">$vout</span><span style="color: #007700">) {<br> </span><span style="color: #FF8000">// Is this output for one of our addresses?<br> </span><span style="color: #007700">if (</span><span style="color: #0000BB">in_array</span><span style="color: #007700">(</span><span style="color: #0000BB">$vout</span><span style="color: #007700">[</span><span style="color: #DD0000">'scriptPubKey'</span><span style="color: #007700">][</span><span style="color: #DD0000">'addresses'</span><span style="color: #007700">][</span><span style="color: #0000BB">0</span><span style="color: #007700">], </span><span style="color: #0000BB">$addresses</span><span style="color: #007700">)) {<br> </span><span style="color: #FF8000">// Has this output been spent yet?<br> </span><span style="color: #007700">try {<br> </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">request</span><span style="color: #007700">(</span><span style="color: #DD0000">'gettxout'</span><span style="color: #007700">, [</span><span style="color: #0000BB">$txid</span><span style="color: #007700">, </span><span style="color: #0000BB">$vout</span><span style="color: #007700">[</span><span style="color: #DD0000">'n'</span><span style="color: #007700">]]);<br> </span><span style="color: #0000BB">$inputs</span><span style="color: #007700">[] = [<br> </span><span style="color: #DD0000">'txid' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$txid</span><span style="color: #007700">,<br> </span><span style="color: #DD0000">'vout' </span><span style="color: #007700">=> </span><span style="color: #0000BB">$vout</span><span style="color: #007700">[</span><span style="color: #DD0000">'n'</span><span style="color: #007700">],<br> ];<br> </span><span style="color: #0000BB">$transaction_amount </span><span style="color: #007700">+= </span><span style="color: #0000BB">CoinTools</span><span style="color: #007700">::</span><span style="color: #0000BB">bitcoinToSatoshi</span><span style="color: #007700">(</span><span style="color: #0000BB">$vout</span><span style="color: #007700">[</span><span style="color: #DD0000">'value'</span><span style="color: #007700">]);<br> }<br> catch (\</span><span style="color: #0000BB">Exception $e</span><span style="color: #007700">) {}<br> }<br> }<br> </span><span style="color: #FF8000">// Remove the miner fee from the transaction amount.<br> </span><span style="color: #0000BB">$transaction_amount </span><span style="color: #007700">-= </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">transactionEstimateFee</span><span style="color: #007700">(</span><span style="color: #0000BB">count</span><span style="color: #007700">(</span><span style="color: #0000BB">$inputs</span><span style="color: #007700">), </span><span style="color: #0000BB">count</span><span style="color: #007700">(</span><span style="color: #0000BB">$outputs</span><span style="color: #007700">));<br> </span><span style="color: #FF8000">// Divide up the pie according to the correct proportions.<br> </span><span style="color: #0000BB">$ratio </span><span style="color: #007700">= </span><span style="color: #0000BB">$transaction_amount </span><span style="color: #007700">/ </span><span style="color: #0000BB">array_sum</span><span style="color: #007700">(</span><span style="color: #0000BB">$outputs</span><span style="color: #007700">);<br> foreach (</span><span style="color: #0000BB">$outputs </span><span style="color: #007700">as </span><span style="color: #0000BB">$address </span><span style="color: #007700">=> &</span><span style="color: #0000BB">$amount</span><span style="color: #007700">) {<br> </span><span style="color: #0000BB">$amount </span><span style="color: #007700">*= </span><span style="color: #0000BB">$ratio</span><span style="color: #007700">;<br> </span><span style="color: #FF8000">// Eliminate dust outputs.<br> </span><span style="color: #007700">if (</span><span style="color: #0000BB">$amount </span><span style="color: #007700">< </span><span style="color: #0000BB">546</span><span style="color: #007700">) {<br> unset(</span><span style="color: #0000BB">$outputs</span><span style="color: #007700">[</span><span style="color: #0000BB">$address</span><span style="color: #007700">]);<br> continue;<br> }<br> </span><span style="color: #0000BB">$amount </span><span style="color: #007700">= </span><span style="color: #0000BB">CoinTools</span><span style="color: #007700">::</span><span style="color: #0000BB">satoshiToBitcoin</span><span style="color: #007700">(</span><span style="color: #0000BB">$amount</span><span style="color: #007700">);<br> }<br> </span><span style="color: #FF8000">// Make sure there is something to send.<br> </span><span style="color: #007700">if (empty(</span><span style="color: #0000BB">$outputs</span><span style="color: #007700">)) {<br> throw new \</span><span style="color: #0000BB">Exception</span><span style="color: #007700">(</span><span style="color: #DD0000">"No bitcoin to send."</span><span style="color: #007700">);<br> }<br> </span><span style="color: #FF8000">// Send the transaction.<br> </span><span style="color: #007700">return </span><span style="color: #0000BB">$this</span><span style="color: #007700">-></span><span style="color: #0000BB">transactionSendNew</span><span style="color: #007700">(</span><span style="color: #0000BB">$inputs</span><span style="color: #007700">, </span><span style="color: #0000BB">$outputs</span><span style="color: #007700">);<br> }<br></span><span style="color: #0000BB">?></span></span>
What are the use cases of transaction forwarding?
Splitting donations among different parties
If a band was getting paid in Bitcoin it could be configured what percentage each band member would receive.
Affiliate marketing
When a sale is made, a third party who provided the lead for the sale could receive part of the funds spent.
Donate percent of revenue to charity
A company could provably show that they are donating a certain percentage of their revenue to a charity. If the funds are always forwarded to the same address for the charity, then a customer can observe the blockchain and check that the correct proportion of their money went to the right place.
Getting funds off an insecure platform
As I mentioned in an earlier blog post, holding funds in a hot-wallet on a server is not very secure. Immediately forwarding the transactions out of harms way alleviates this problem.
I have added support for this to Coin Tools payments. If the forwarding address is set in the payment type, payments will be forwarded to it after 1 confirmation.
An example of this can be seen on the blockchain.