Get your Bitcoin address using Ethereum smart contract

Get your Bitcoin address using Ethereum smart contract

Ethereum and Bitcoin are both using the same type of encryption, the ECC (Elliptic Curve Cryptography) over the same graph (256k1). While it’s not really recommended, the same key pair can be used both for Bitcoin and Ethereum.

A simple Solidity code can be used to get the Bitcoin address of a public key. Such a code can run locally (as a constant function) on the Ethereum Virtual Machine to save gas, or as a regular Ethereum transaction.

The code in this example requires the user to insert their public key in its uncompressed format as an input; then it produces the binary address that matches that uncompressed public key for the main Bitcoin network. The code can be easily modified to work with compressed public keys as well (just remove the yPoint and add the side of the ECC graph). The code can also be amended to give the binary address of other testnet/namecoin.

 

How to create a Bitcoin address

The most basic process of deriving Bitcoin address from a public key is set in the following technical documentation.

 As you can clearly see, there’s very little to it than just hashing and appending.

Recreating the process in Solidity

First, let’s generate a random keypair using bitaddress.org. Under the tab “wallet details” we can see the uncompressed public key.

The public key
xPoint = C4BB8E42F7DA5504A456C16BE533549DA4FE580279382478F3365FF7CCBF032D
yPoint = 68A73547E809F1ABFAA51D10019E8AC682D1205448042326E9E3B91841CB9FA7

Now let’s create our smart contract in Solidity:

pragma solidity ^0.4.11;

contract BitValid{
	
	bytes32 constant mask4 = 0xffffffff00000000000000000000000000000000000000000000000000000000;
	bytes1 constant network = 0x00;


	function getBitcoinAddress(
			bytes32 _xPoint,
			bytes32 _yPoint)
			constant
			returns(
				bytes20 hashedPubKey,
				bytes4 checkSum,
				bytes1 network)
	{
		hashedPubKey 	= getHashedPublicKey(_xPoint, _yPoint);
 		checkSum 	= getCheckSum(hashedPubKey);
 		network 	= network;
	}

	function getHashedPublicKey(
			bytes32 _xPoint,
			bytes32 _yPoint)
			constant
			returns(
				bytes20 hashedPubKey)
	{
		var startingByte = 0x04;
 		return ripemd160(sha256(startingByte, _xPoint, _yPoint));
	}

	function getCheckSum(
			bytes20 _hashedPubKey)
			constant
			returns(
				bytes4 checkSum)
	{
		var full = sha256((sha256(network, _hashedPubKey)));
		return bytes4(full&mask4);
	}
}

The function getBitocinAddress() takes the x and y coordinate of the public key from the user, both are 32 bytes long (the uncompressed public key) and will return 3 variables, the hashed public key (bytes20), the checksum (bytes4) and the network starting byte (bytes1).

The network starting byte is currently hard codded to 0x00 (the main starting code). You can change this code to work with any other test network.

The hashed public key is obtained by hashing the public key (both x and y coordinates) with the starting byte 0x04 twice (as described in the technical documentation). Once with sha256 and then again with ripemd160. The finale result is 20 bytes long.
function getHashedPublicKey(
		bytes32 _xPoint,
		bytes32 _yPoint)
		constant
		returns(
			bytes20 hashedPubKey)
{
	var startingByte = 0x04;
	return ripemd160(sha256(startingByte, _xPoint, _yPoint));
}
After we got the hashed public key, we’ll prepend the network byte to it and hash it again twice using the sha256 function. The result of 32 bytes long is used to construct the checksum, a special 4 bytes that are used to allow another user to verify that the Bitcoin address they’re sending to is indeed a valid address.
bytes32 constant mask4 = 0xffffffff00000000000000000000000000000000000000000000000000000000;

function getCheckSum(
		bytes20 _hashedPubKey)
		constant
		returns(
			bytes4 checkSum)
{
	var full = sha256((sha256(network, _hashedPubKey)));
	return bytes4(full&mask4);
}
We don’t need all of the 32 bytes, only the first 4 bytes, but slicing variables is a hard thing to do in Solidity. Luckily, Solidity does allow for easy bit manipulation and masking. You’ll need to create a mask of 32 bytes to match the 32 bytes of the sha256 output. This mask should take only the first 4 bytes, as they’re the real checksum.
The full result (32 bytes) = 0x4c30ed507a508af52063560ff8f1c09e66be0587868a0b8ca21ab337440e4e8e
Mask for the first 4 bytes = 0xffffffff00000000000000000000000000000000000000000000000000000000
checksum = 0x4c30ed50

The results

At the end of the day, we have the following three components to return to the user, the network byte (currently hard coded), the hashed public key and the checksum. These are the three components that make up a Bitcoin address.

However, this isn’t the last step. In Bitcoin, a special type of encoding is used called base58. The current code doesn’t convert the result into base58 (I’ll leave it for another day), so we’ll be forced to do this step manually.

The following website provides some tools to convert our bytecode into base58. This is basically the final Bitcoin address.

At the end of the day

Using Solidity to retrieve the Bitcoin address that matches a specific public key (and therefore, a private key as well) might be useful when you’re trying to create a smart contract that maps some events between entities on both blockchains and I suspect might have some value when dealing with identities. The procedure isn’t cheap on gas but can be done locally using the EVM. It’s a shame that there’s no access to the bytecode of the transactions in Solidity since it could have made the process of finding the Bitcoin address of the message sender automated.

One thought on “Get your Bitcoin address using Ethereum smart contract

  1. Thanks for the article, I would like to try this for cross blockchain RSK – > ETH / ETH -> RSK as a proof of concept for transferring a smart contract between networks …perhaps for routing to the least congested network (or other reasons).

Leave a Reply

Your email address will not be published. Required fields are marked *