Skip to content

HD keys (BIP32)

Hierarchical deterministic (HD) keys derive a whole tree of key pairs from a single seed. A wallet only needs to back up that one secret, and can still use a fresh Address for every payment. Bitcoin4J’s HD code is BitcoinJ’s, unchanged. We use DeterministicKey for a node of the tree, HDKeyDerivation for deriving, and DeterministicHierarchy for walking paths. The HD keys appendix explains the scheme.

The examples use the seed of BIP32 test vector 1, so you can check the results against the specification.

byte[] seed = Utils.HEX.decode("000102030405060708090a0b0c0d0e0f");
DeterministicKey master = HDKeyDerivation.createMasterPrivateKey(seed);
String xprv = master.serializePrivB58(NetworkType.MAIN);
String xpub = master.serializePubB58(NetworkType.MAIN);

The seed is usually the output of a mnemonic. serializePrivB58() and serializePubB58() give us the xprv and xpub strings for NetworkType.MAIN, and tprv and tpub for the test networks.

HDKeyDerivation.deriveChildKey() derives one level at a time. A ChildNumber is an index plus a hardened flag.

// m/0H/1 is a hardened child, followed by a normal child.
DeterministicKey account = HDKeyDerivation.deriveChildKey(master, new ChildNumber(0, true));
DeterministicKey child = HDKeyDerivation.deriveChildKey(account, new ChildNumber(1, false));
String path = child.getPathAsString();

DeterministicHierarchy derives a full path in one call, and caches the keys along the way.

// Paths mark the hardened steps with H (or with ').
DeterministicHierarchy hierarchy = new DeterministicHierarchy(master);
DeterministicKey key = hierarchy.get(HDPath.parsePath("m/0H/1/2H/2"), false, true);

In the preceding example the arguments after the path are:

  • relativePath, where false means the path starts at the root,
  • create, where true means we derive any keys that are not cached yet.

HDPath.parsePath() accepts either marker for a hardened step: H, as in m/44H/0H/0H/0/0, or the apostrophe used by most wallets, and by BIP44 itself.

// The apostrophe that BIP44 and most wallets use marks a hardened step too.
assertEquals(HDPath.parsePath("m/44H/0H/0H"), HDPath.parsePath("m/44'/0'/0'"));

An extended Public Key derives the public half of every non-hardened child. This means a server can generate receiving Addresses from an account xpub without ever holding a Private Key.

// A server holding only the account xpub can derive receiving keys, but cannot sign.
DeterministicKey watchOnly = DeterministicKey.deserializeB58(accountXpub, NetworkType.MAIN);
DeterministicKey receiving = HDKeyDerivation.deriveChildKey(watchOnly, new ChildNumber(1, false));
boolean canSign = receiving.hasPrivKey();

Do note that deriving a hardened child from a public-only key throws IllegalArgumentException.

A DeterministicKey is an ECKey, so it converts directly into the key types the rest of the library takes.

// A DeterministicKey is an ECKey, so it wraps into the key types the rest of the library uses.
PrivateKey privateKey = new PrivateKey(child, true, NetworkType.MAIN);
PublicKey publicKey = PublicKey.fromBytes(child.getPubKey());
Address address = Address.fromKey(NetworkAddressType.MAIN_PKH, publicKey);

deserializeB58(xprv, network) reads a private extended key back in, in the same way as the xpub above.