Bitcoin for beginners, Part 3: The BitCoinJ API

Build a Java-based Bitcoin transaction client

1 2 3 Page 2
Page 2 of 3

Wallets and keys

If you participate in the Bitcoin economy, you likely keep all of your riches in your wallet. The wallet is nothing more than a local data file that contains serialized objects representing all of your Bitcoin transactions and a cache of unused addresses. The sum of your incoming and outgoing transaction amounts is the amount of Bitcoins in your wallet. In this section we'll use BitCoinJ's Wallet object to create a wallet data file, populate it with five addresses, and save it to disk.

The Wallet class implements the Serializable interface to enable us to persist it to disk or some other more permanent storage medium. Specifically, methods loadFromFile(File) and the corresponding saveToFile(File) read and write wallet files. We'll be using loadFromFile(File) to write a newly created wallet object to a file.

Note that BitCoinJ wallet files are not compatible with wallet files created by the official Bitcoin client.

Creating and storing keys

The Wallet class has a public member named keychain that is an ArrayList of type ECKey, which is used to store all EC key pairs in the wallet. The addKey(ECKey) method is used to add key pairs, but there is currently no method for removing them. This makes sense because it shouldn't be easy for users or programs to delete private keys: a private key is required to access funds sent via its corresponding public key. Without a key pair in the wallet or backed up somewhere, any sent funds would be lost forever.

Given a public key or its hash you can check whether a matching key pair is in the keychain and retrieve it using the following convenience methods:

findKeyFromPubHash(byte[] pubkeyHash)
isPubKeyHashMine(byte[] pubkeyHash)
findKeyFromPubKey(byte[] pubkey)
isPubKeyMine(byte[] pubkey)

Note that the only constructor for the Wallet class takes a NetworkParameters object as an argument. This ensures that you cannot mix transactions from the production and test networks in the same wallet.

Keys, wallet ...

In Listing 3 we default to the testnet (line 13) and declare a Wallet object (line 16) that will later require a File object (line 17) to persist itself to disk. In our main try-catch block, we then initialize the Wallet object to use the testnet (line 20). Next, we loop five times, each time creating a brand-new key pair in the form of an ECKey object, which we add to the wallet (line 26).

Having something in our wallet, we then proceed to save it to disk (line 30). If you were to take a look at your file system now, you would see a file named test.wallet. On line 37, we grab the first key in the keychain ArrayList and output it (line 40), followed by the dump of the whole wallet (line 43). Finally, we want to determine whether a particular key has been added to the wallet, so we ask the wallet to compare a given hash to the hashes of the public keys in the wallet (line 47).

Listing 3. CreateWallet

001|package com.waferthin.bitcoinj;
002|
003|import java.io.File;
004|import java.io.IOException;
005|
006|import com.google.bitcoin.core.*;
007|
008|public class CreateWallet {
009|
010|    public static void main(String[] args) {
011|
012|        // work with testnet
013|        final NetworkParameters netParams = NetworkParameters.testNet();
014|
015|        // Try to read the wallet from storage, create a new one if not possible.
016|        Wallet wallet = null;
017|        final File walletFile = new File("test.wallet");
018|        
019|        try {
020|            wallet = new Wallet(netParams);
021|            
022|            // 5 times
023|            for (int i = 0; i < 5; i++) {
024|                
025|                // create a key and add it to the wallet
026|                wallet.addKey(new ECKey());
027|            }
028|            
029|            // save wallet contents to disk
030|            wallet.saveToFile(walletFile);
031|            
032|        } catch (IOException e) {
033|            System.out.println("Unable to create wallet file.");
034|        }
035|        
036|        // fetch the first key in the wallet directly from the keychain ArrayList
037|        ECKey firstKey = wallet.keychain.get(0);
038|
039|        // output key 
040|        System.out.println("First key in the wallet:\n" + firstKey);
041|        
042|        // and here is the whole wallet
043|        System.out.println("Complete content of the wallet:\n" + wallet);
044|        
045|        // we can use the hash of the public key
046|        // to check whether the key pair is in this wallet
047|        if (wallet.isPubKeyHashMine(firstKey.getPubKeyHash())) {
048|            System.out.println("Yep, that's my key.");
049|        } else {
050|            System.out.println("Nope, that key didn't come from this wallet.");
051|        }
052|    }
053|}

Here is the output produced by the CreateWallet class:

First key in the wallet:
pub:04cf1b56e809dd615663d918824688264d81dbd3642550a82545df5bfa0dfeb5c1e1ba0d97fa278853e121678d13eb2f7e061281957933e9d4bd03893e80e14e0e priv:00e657901ce15ec38c234060e79dd99cda6f1d82ef4b3c90da65c84d6e9ce06a9e
Complete content of the wallet:
Wallet containing 0.00 BTC in:
  0 unspent transactions
  0 spent transactions
  0 pending transactions
  0 inactive transactions
  0 dead transactions

Keys:
  addr:n1QCia54L1MDR4xjQStM48vGnfQf62kh8p pub:04cf1b56e809dd615663d918824688264d81dbd3642550a82545df5bfa0dfeb5c1e1ba0d97fa278853e121678d13eb2f7e061281957933e9d4bd03893e80e14e0e priv:00e657901ce15ec38c234060e79dd99cda6f1d82ef4b3c90da65c84d6e9ce06a9e
  addr:moMovkB3uGTZrf66f5WddcNVhq6MuZUajJ pub:0423a79d67d612e44d06228bc47c9ac1ead8124929280ac827c2aa301075e27909ec2b5502eee7be40e08fba42a7e5c2daa91b1641fe1c0368068f65ea14c27d60 priv:00862ef8769d848ae56eebd211652f72667b353093f5db6139e81b7a787544a039
  addr:mrukZip4D6TG8TxEhhBLkKZvQY4fCPZxZn pub:04c11cdfbc6fbf838a03bd4d49df1957330a4105529054002f87a83d726a632ad7da257c0997aa52fa484382c675d6835f9f78b3c65366a2f36953e992404de63f priv:460343ee1e983fd0ad0ca0023f1b1aa4269ba34c7f17e8463856085669c0bc23
  addr:mgro3Ned1KakmVbP8hPf4iDDo6KDTpCzGL pub:046212ef2216273af27185493de1197ee4a21fef46dee0c27f026b765473c0cd1e1d62ea9f1ea2dbb16103b3c96bb31d78e102b2387ac0b03d67d671226dbb2117 priv:5a42597d63515df1a60ac0090ae55f52e0a8e6ec74e320041cb52605a643c178
  addr:myQqSQpk5mN1e4it1r3dxdyB6Cuu1tWjjo pub:04222d33fb1f89a217804a0400d34b48c08808fbbd22af5e408f04e45597ca3c061ca6dcfe04e4b483c3099caa718e4e5ba6260501b7d24ae9dcfa15844afffe13 priv:00ec4b1abee48a43fe4b12eff1e8fdacabd392554dc12f1cf1156467218cd92189

Yep, that's my key.

You may have noticed the the Wallet object's toString() method also reported on various types of transactions, such as unspent, spent, pending, inactive, and dead. Before the end of this article we'll use a wallet to generate an actual transaction and send it over to the network to a recipient.

Getting the genesis block

Next, we'll connect to a peer node on the Bitcoin network and request from it a single block. Specifically, we'll connect to a locally running Bitcoin node and retrieve the testnet's genesis block. The genesis block is the first in the sequence of blocks in the block chain. If you recall, the block chain is a data structure similar to a linked list that contains blocks of hierarchically organized transaction hashes, as well as the transactions themselves.

Each block header contains a hash that is a pointer to the previous block in the block chain. We can use the hash pointer to request a block from its network peers. Doing so lets us traverse the block chain and retrieve all the blocks needed to reconstruct the complete transaction history of the Bitcoin economy. The only block without a valid hash pointer is the genesis block, which has no previous block. In this example we will learn how to retrieve the genesis block by its hash, but we could use the same technique to retrieve any other block or to traverse the block chain.

You can also view the testnet's genesis block using Bitcoin Block Explorer: http://blockexplorer.com/testnet/b/0.

Rather than asking the node to return a single block, we could be downloading all blocks that would, in the right sequence, constitute the block chain. But before we look at the code itself, we need to study a couple of the classes in detail.

BlockChain and BlockStore

The BlockChain class understands the kind of data structure the block chain represents. Using the add(Block) method, one can add any number of blocks to the chain and the BlockChain class will verify the block's validity and try to place it at its correct location within the block chain list. As the Bitcoin economy expands, the block chain will continue to get longer. Clients download, store, and access the complete block chain. With the requirements this places on disk space and memory, it makes sense that BitCoinJ's developers decided to decouple the actual storage of the blocks in the chain from the data structure and logic, hence the BlockStore interface.

BlockStore contains four method signatures that must be implemented by any class that provides the actual block storage: put(StoredBlock) and get(Sha256Hash) allow developers to add and retrieve blocks, respectively. getChainHead() and setChainHead(StoredBlock) are accessor methods for the most recent block in the chain, the head.(As previously mentioned, BitCoinJ is not a full implementation of the Bitcoin protocol. One significant difference is that it stores only the block headers and discards the underlying transactions that are usually part of the block.)

Three classes currently implement the BlockStore interface and provide block storage. MemoryBlockStore keeps block headers in memory, which is fast and convenient, but not sustainable as the block chain grows. DiskBlockStore functions like MemoryBlockStore, but also saves the headers to disk. Lastly, BoundedOverheadBlockStore is an implementation intended for resource constrained environments, trading constant resource usage for performance predictability.

Given that we will not be downloading the whole block chain, the MemoryBlockStore will work just fine for our example. The above three BlockStore implementations are the ones currently included in the Bitcoin project. Various others are being worked on, however, including ones based on SQLite and various flavors of NoSQL. These are often glorified HashMaps and thus a perfect fit for Bitcoin.

Peer

Another class we need to understand for our example is the Peer class. It handles the high-level communications between our client and a particular node on the network. Under the hood, it depends on classes such as NetworkConnection, Block, and BlockChain to do its work. In standard use of BitCoinJ, the application will connect to any number of nodes and instantiate as many Peer objects to handle the connections. After calling the connect() method on the Peer object, the code handling messages from other nodes runs in a loop and needs to run in a thread, which is what you will see in the next example.

Requesting the genesis block from a network peer

Given that knowledge of BitCoinJ's internals we can quickly step through the next example. Listing 4 starts by instantiating an object representing parameters for the testnet (line 19) and uses it to initialize a MemoryBlockStore object (line 22). We declare a BlockChain object (line 25) and initialize it inside a try-catch block (line 30). The BlockChain object requires both a NetworkParameters and BlockStore object as arguments.

We then instantiate a Peer object while telling it to use the testnet, connect to a node on localhost, and use the previously instantiated BlockChain object for storage (line 33). After actually connecting to the peer node (line 36), we execute the Peer object's message handling loop (line 39). A more typical use case would be to have multiple peer node connections, in which case we would probably run each Peer object's message-handling loop in its own thread. Because we're dealing with a single node directly, that is not necessary in our example. Having found the hash of the testnet's genesis block on the Bitcoin Block Explorer site, we create a Sha256Hash object (line 50) and ask the peer object to request the block with the corresponding hash from the peer node (line 54). After receiving the block (line 58), we output it, which the Block class's overwritten toString() method does quite nicely (line 59).

Listing 4. FetchGenesisBlock

1 2 3 Page 2
Page 2 of 3