Libbitcoin: Annonymizing Bitcoin with a CoinJoin Transaction

One way of anonymizing bitcoin transactions is by using coinjoin. Coinjoin is a type of transaction that combines inputs from many different people’s wallets and creates outputs paying to several different pubkeys which, from the perspective of a blockchain observer, cannot be tied to an individual input from the set of inputs.

A 3-way coinjoin, would have you all combine UTXOs and pubkey hashes to construct a single transaction. After that you can individually sign the transaction and broadcast it to the network.

Coding a coinjoin transaction is a lot like coding a regular transaction except that it has multiple very specific inputs and outputs. For this walk through I have demoed a coinjoin transaction on the testnet. A lot of the necessary code is going to look similar to code used when building a raw transaction.

First include and namespace libbitcoin:


#include <bitcoin/bitcoin.hpp>
#include <bitcoin/client.hpp>


using namespace bc;
using namespace bc::wallet;
using namespace bc::machine;
using namespace bc::chain;


First, we can make the outputs by combining the three pubkeys and amounts.

output::list makeOuputs(uint64_t amount, payment_address address1, payment_address address2, payment_address address3)
{
	output output1(amount, script(script().to_pay_key_hash_pattern(address1.hash())));
	output output2(amount, script(script().to_pay_key_hash_pattern(address2.hash())));
	output output3(amount, script(script().to_pay_key_hash_pattern(address3.hash())));

	return output::list {output1, output2, output3};

}

Then, construct the transaction in the main function.

int main()
{
	payment_address destination1("n2ge1S4bLDvJKx8AGXrK5JHY2D5cReVytu");
	payment_address destination2("mnrnjVFimDFrNkszzMtecr4yrMKmEuMRbv");
	payment_address destination3("n2BPFTRKLtM6VQGN6tCWCaEbBuFTEo5P6r");

	transaction coinJoin = transaction();

	uint64_t amount = 100000000;
	output::list outputs = makeOuputs(amount, destination1, destination2, destination3);
	coinJoin.set_outputs(outputs);

Now, making the inputs is going to require connecting to a server. In order to get the UTXO by the sender’s address, we need to write a libbitcoin-server query function. Here we can use asynchronous callback functions to return a list of utxo points at least as large as the amount we queried from the server. .

points_value getUTXOs(payment_address Addy, uint64_t amount)
{
	client::connection_type connection = {};
	connection.retries = 3;
	connection.timeout_seconds = 8;
	connection.server = config::endpoint("tcp://testnet1.libbitcoin.net:19091");
	client::obelisk_client client(connection);

	points_value val1;
	static const auto on_done = [&val1](const points_value& vals) {

		std::cout << "Success: " << vals.value() << std::endl;
		val1 = vals;
		

	};

	static const auto on_error = [](const code& ec) {

		std::cout << "Error Code: " << ec.message() << std::endl;

	};

	if(!client.connect(connection))
	{
		std::cout << "Fail" << std::endl;
	} else {
		std::cout << "Connection Succeeded" << std::endl;
	}

	client.blockchain_fetch_unspent_outputs(on_error, on_done, Addy, amount, select_outputs::algorithm::greedy);
	
	client.wait();
	
	
	//return allPoints;
	return val1;


}

Once we have a way to get UTXOs we can construct the inputs with just a pubkey hash, AKA an address, and the amount we want to join. Note that we’re also going to add an extra output for the change from the transaction, which will not be anonymized.

transaction makeInput(payment_address Addy, transaction tx, uint64_t amount)
{
	points_value UTXOs = getUTXOs(Addy, amount);
	input::list inputs {};
	
	for (auto utxo: UTXOs.points)
	{
		input workingInput = input();
		workingInput.set_previous_output(output_point(utxo));

		std::cout << encode_base16(utxo.hash()) << std::endl;
		workingInput.set_script(script(script().to_pay_key_hash_pattern(Addy.hash())));
		workingInput.set_sequence(0xffffffff);
		inputs.push_back(workingInput);
	}

	uint64_t change = UTXOs.value() - amount - 1000;
	script outScript = script(script().to_pay_key_hash_pattern(Addy.hash()));

	std::cout << "change: "<< change << std::endl;
	tx.outputs().push_back(output(change, outScript));
	
	extend_data(tx.inputs(), inputs);
	return tx;
}

Now, back in the main function we can call this makeInput function for the first wallet, but we are going to want to have a function to sign it with first.

The signing function takes an ec private key and the coinjoin transaction and loops through the inputs looking for inputs with previous output scripts that match the script derived from private key.(the if statement). If the scripts match, we sign that input with a sighash all-anyone can pay pattern which allows others to add inputs. Then, the derived public key is combined with the signature and added to the input structure.


transaction sigScript(ec_private wal, transaction tx)
{
	int index = 0;
	for (auto input: tx.inputs())
	{
		if(input.script() == script(script().to_pay_key_hash_pattern(wal.to_payment_address().hash())))
		{
			endorsement sig;
			script().create_endorsement(sig, wal.secret(), input.script(), tx, index, all_anyone_can_pay);
			operation::list ops {operation(sig), operation(to_chunk(wal.to_public().point()))};
			script scriptSig(ops);
			input.script().clear();
			input.set_script(scriptSig);
			tx.inputs()[index] = input;
		}
		index++;
	}
	return tx;

}

Now, normally each participant would sign their portion of the transaction separately and thus protect their private keys. However, for this example, I am going to declare all three private keys at the top of the main function and call the inputs and signing utilities.

	ec_private wal1(bitcoin_hash(decode_mnemonic(split("ENTER YOUR MNEMONIC "))),0xEF6F, 1);
	ec_private wal2(bitcoin_hash(decode_mnemonic(split("ENTER YOUR MNEMONIC "))),0xEF6F, 1);
	ec_private wal3(bitcoin_hash(decode_mnemonic(split("ENTER YOUR MNEMONIC"))),0xEF6F, 1);


Now call the signing functions for each wallet and print out the resulting raw transaction.


	coinJoin = makeInput(wal1.to_payment_address(), coinJoin, amount);
	coinJoin = makeInput(wal2.to_payment_address(), coinJoin, amount);
	coinJoin = makeInput(wal3.to_payment_address(), coinJoin, amount);

	coinJoin = sigScript(wal1, coinJoin);
    coinJoin = sigScript(wal2, coinJoin);
	coinJoin = sigScript(wal3, coinJoin);
	std::cout << “\n” << encode_base16(coinJoin.to_data(1)) << std::endl;
	

Then, using the same broadcastTX function we learned how to code in my broadcasting a transaction post, we can submit this transaction to the network.

void broadcastTX(transaction tx)
{
	client::connection_type connection = {};
	connection.retries = 3;
	connection.timeout_seconds = 8;
	connection.server = config::endpoint("tcp://testnet1.libbitcoin.net:19091");
	client::obelisk_client client(connection);

	if(!client.connect(connection))
	{
		std::cout << "Fail" << std::endl;
	} else {
		std::cout << "Connection Succeeded" << std::endl;
	}
	
	static const auto on_done = [](const code& ec) {

		std::cout << "Success: " << ec.message() << std::endl;

	};

	static const auto on_error2 = [](const code& ec) {

		std::cout << "Error Code: " << ec.message() << std::endl;

	};

	client.transaction_pool_broadcast(on_error2, on_done, tx);
	client.wait();
}

And in the main function we add a call to this new function.

broadcastTX(coinJoin);

Compile with libbitcoin:

alpha$ g++ -std=c++11 -o join coinjoin.cpp $(pkg-config --cflags libbitcoin --libs libbitcoin libbitcoin-client)
alpha$ ./join

The output should look like this:
coinjoinOutput

Inspecting the transaction on a blockchain explorer illustrates how this method obfuscates bitcoin transactions. An observer of the transaction, who has tied my bitcoin address to my identity can see after the coinjoin transaction that I have sent 1 bitcoin away from my address but since it was combined with transactions from other addresses all with 1 bitcoin sent to a separate address, the observer has no way of knowing which of the three addresses I sent my coins to.

coinjoinExplorer

The observer can only infer that any of those three outputs has a 1/3 probability of being mine. To achieve greater anonymity, coins can be joined several time over with many many participants. An ideal coinjoining technique would minimize the amount of trust and data shared between participants in the join.

As always you can find the entire code example on github.

Leave a Reply

Your email address will not be published.