Libbitcoin: Hash Lock Contracts

Libbitcoin: Hash Lock Contracts

Hash Lock Contracts:

Hash lock contracts allow bitcoin to be spent to a contract which locks them up, requiring the spender to produce some data that resolves the contracts hash function.

The contract is represented as a bitcoin address, subjecting any funds spent to it to the contracts hash condition. Basically, It’s another type of password you can add to a bitcoin transaction other than a private key signature.

These types of bitcoin scripting features allow for more complex trustless transaction models for smart contracts, arbitration, or cross-chain trading.

The hash-lock contract will look like this:

OP_IF OP_DUP OP_HASH160 [e82f89457f9efaab09b3222b5f7f82b4ab826832] OP_EQUALVERIFY OP_CHECKSIG OP_ELSE OP_HASH160 [7e88c8277e78610110c79a77eb0d340fba0c2775] OP_EQUAL

Basically, this script has two parts separated by the OP_IF and OP_ELSE statements. The first IF is a typical pay-to-key-hash-pattern which says that this output can be spent only if the spender provides a public key which when hash160-ed(sha256 followed by ripemd160 hash) equals the address and a signature which signs the transaction with the corresponding private key. The ELSE clause says that this output can also be spent if the spender is able to provides some pre-image which when hash160ed equals the hash in the script.

This script represents a contract that says:

“This money can be spent by anyone who controls address n2ge1S4bLDvJKx8AGXrK5JHY2D5cReVytu or anyone who can provide the secret data which hashes to 7e88c8277e78610110c79a77eb0d340fba0c2775.” 

In order to make this contract payable, we must turn it into a pay-to-script-hash address. Easy enough, using the same process used to create P2SH multisig addresses. The entire script is serialized and then hash160ed which, again, is just a sha256 hash followed by a ripemd160 hash. This produces a 20-byte hash of the contract, for example:

bcd2f1e567f69b46d795e47fa5ee571d8502eb6d

This hash can then be base58check-encoded to create an address for the contract. Note, mainnet P2SH addresses start with a 3 while testnet P2SH addresses start with a 2. which should look something like this:

2NATdj4GU7WFEW85D3qhojTxwtDMo8WB3iS

Now all the coin sent to this address will be subject to the terms of the contract that it is derived from.

Coding The Contract:

Making this contract in libbitcoin is going to be very similar to how we made and spent from a multsig address.


#include <bitcoin/bitcoin.hpp>
#include <bitcoin/client.hpp>
#include "HD_WalletTESTNET.cpp"


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



 First, I’ll need to include the relevant namespaces from libbitcoin including chain, machine and wallet. I’ll also want to include my HD_wallet utility that I built here.

The basic steps from here are going to look a lot like my raw transaction tutorial except with a more complex script. So, in the main function I’ll start building my transaction and input with a hard coded hash value.

int main()
{
	transaction hashLock = transaction();
	
	hash_digest utxoHash;
	decode_hash(utxoHash, "fddde55c869ec2ae040f37dc55790f3f2ce2e762a9ca8670f799c057d7ee3ecc");
	input input1 = input();
	input1.set_previous_output(output_point(utxoHash, 0));

}

Once I have the input set up, I can move on to the contract’s script which I can build using the script class’ from_string method. This way I can write out the script using mnemonics, note that the OP_ prefix can be left out and raw data must be placed in brackets.

int main()
{
	script redeemScript = script();
	redeemScript.from_string("if dup hash160 [e82f89457f9efaab09b3222b5f7f82b4ab826832] equalverify checksig else hash160 [7e88c8277e78610110c79a77eb0d340fba0c2775] equal");

}

We can now create an address from the contract by passing the script and “0xC4” the testnet p2sh version prefix to the payment address constructor. We can than output it to the console.

	payment_address contractAddress(redeemScript, 0xC4);
	std::cout << "\n" << contractAddress.encoded() << '\n' <<std::endl;

Now if we run this program we can see the address of our contract, the address we want to pay into in order to lock up the bitcoins under those terms. We can now construct our output by creating a pay-to-script-hash-pattern script and passing it our contracts hash. Then we just add our payment value to the output and push both the output and input to the transaction.

	script lockingScript = script(script().to_pay_script_hash_pattern(contractAddress.hash());
	output output1(299996999, lockingScript);
	hashLock.inputs().push_back(input1);
	hashLock.outputs().push_back(output1);

Once the transaction is built we just need to sign it and add the signature script. Here, I’m using an my HD_Wallet utility to derived my wallet from a mnemonic. Then we can output the raw transaction to the display.

	HD_Wallet wallet(split("chase your scorpion slab mnemonic imitate goes blouse here dignity message strong")); 
	endorsement sig;
	script().create_endorsement(sig, wallet.childPrivateKey(1).secret(), script(script().to_pay_key_hash_pattern(wallet.childAddress(1).hash())), hashLock, 0, all);
	operation::list ops = {operation(sig), operation(to_chunk(wallet.childPublicKey(1).point()))};
	hashLock.inputs()[0].set_script(script(ops));
	std::cout << encode_base16(hashLock.to_data(1)) << std::endl;

Now, to “withdraw” money from this address we are going to need to create a transaction spending money to our address and resolve the contract script. This concept can be resolved and spent either with the private key or the pre-image of the hash I added to the script.

So with the current agreement I have, the coins with in the address can be spent by myself at any time since I control the address n2ge1S4bLDvJKx8AGXrK5JHY2D5cReVytu. Now say I have revealed the pre-image based on some pre-arranged deal. You now know that secret pre-image is “Libbitcoin” which base16-encoded is 4c6962626974636f696e.

A transaction can now be constructed to withdraw the funds without a private key using the pre-image of the hash lock. Our unlocking script will have to bypass the first IF statement since we want to resolve the hashLock not the signature. To do this we simply pass OP_0 in front of our script causing the IF to evaluate false and pass control to the ELSE block. If we wanted to execute the IF block we would have to pass OP_1. Our unlocking script will look like this:

[Hash] + FALSE + [Redeem Script]

[4c6962626974636f696e] OP_0 [6376a914e82f89457f9efaab09b3222b5f7f82b4ab82683288ac67a9147e88c8277e78610110c79a77eb0d340fba0c277587]

Built into our withdraw transaction script:


transaction resolveContract(transaction contract, script redeemScript)
{
        //Address to withdraw money to
	payment_address withdrawAddress("mtawgig12q69tMuppx2xaQVhBHP1nuHZaA");
	transaction spendContract = transaction();

	output_point contractUTXO(contract.hash(), 0);
	//Secret pre-image
	data_chunk preImage;
	decode_base16(preImage, "4c6962626974636f696e");
        //The data that resolves the contract is pushed first, then the serialized contract script is included
        // this resolves the P2SH format
	script unlockingScript({operation(preImage), operation(opcode(0)), operation(redeemScript.to_data(0))});




	input input1(contractUTXO, unlockingScript, 0xffffffff);

	script lockingScript(script().to_pay_key_hash_pattern(withdrawAddress.hash()));
	output output2 = output(299993999, lockingScript);

	spendContract.inputs().push_back(input1);
	spendContract.outputs().push_back(output2);

	return spendContract;
}


This function can now be called from main and the resulting transaction can be displayed.

	std::cout << "\n" << encode_base16(resolveContract(hashLock).to_data(1)) << std::endl;

So, broadcasting the first transaction should send bitcoins to the contract’s address while the second transaction unlocks them with the hash pre-image and sends them to a withdraw address.

These conditional and complex scripting operations can be combined in various different ways with other scripting features like: checklocktime, checksequence and checkmultisig to create complex contracts and relationships like atomic swapping, zero-knowledge proof and multi-party arbitration.

console output:

hashLock

With the raw transaction you can broadcast them to the network on the browser through blockcypher, the command line using libbitcoin-explorer or using a libbitcoin-client function in the code.

Contract deposit transaction.contractTransaction

Withdraw from the contract.

resolveHash

As always full code can be found on github.

Leave a Reply

Your email address will not be published.