Skip to content

Mnemonic seeds (BIP39)

A BIP39 mnemonic is a list of 12 to 24 words that encodes random entropy plus a checksum. Users write it down, and software turns it into the seed for an HD key tree. The mnemonics appendix in the Developer Guide describes the encoding. In Bitcoin4J the MnemonicCode class handles all of it, with the English word list built in.

// 16 bytes of entropy give us 12 words, and 32 bytes give 24.
byte[] entropy = new byte[16];
new SecureRandom().nextBytes(entropy);
MnemonicCode mnemonicCode = new MnemonicCode(); // the English BIP39 word list
List<String> words = mnemonicCode.toMnemonic(entropy);

The entropy length decides the number of words. 16, 20, 24, 28 or 32 bytes give us 12, 15, 18, 21 or 24 words, and any other length throws MnemonicException.MnemonicLengthException.

Please note that the phrase is only as unpredictable as the entropy bytes, so we always use a SecureRandom for them.

List<String> words = Arrays.asList(
"abandon", "abandon", "abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "abandon", "abandon", "about");
// The passphrase is optional. We pass "" for none (null throws).
byte[] seed = MnemonicCode.toSeed(words, "TREZOR");

toSeed() is static, and does not check the words. Any list produces a seed. The optional passphrase (BIP39 calls it an extension word) changes the seed completely, so the same phrase with a different passphrase is a different wallet.

The value above is the official BIP39 test vector.

We check what a user typed before we derive anything from it.

MnemonicCode mnemonicCode = new MnemonicCode();
List<String> typo = Arrays.asList(
"abandon", "abandon", "abandon", "abandon", "abandon", "abandon",
"abandon", "abandon", "abandon", "abandon", "abandon", "abandon");
// check() throws a MnemonicException subclass for a bad length, an unknown word or a bad checksum.
assertThrows(MnemonicException.MnemonicChecksumException.class, () -> mnemonicCode.check(typo));

check() throws one of:

  • MnemonicLengthException, for the wrong number of words,
  • MnemonicWordException, for a word that is not in the list,
  • MnemonicChecksumException, when all the words are valid but the checksum does not match. That usually means a typo or a swapped word.

toEntropy() runs the same checks and returns the entropy bytes.

Let’s put the common wallet flow together: phrase, seed, master key, BIP44 path and finally an Address.

List<String> words = Utils.WHITESPACE_SPLITTER.splitToList(
"abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
byte[] seed = MnemonicCode.toSeed(words, "");
DeterministicKey master = HDKeyDerivation.createMasterPrivateKey(seed);
DeterministicKey first = new DeterministicHierarchy(master)
.get(HDPath.parsePath("m/44H/0H/0H/0/0"), false, true);
Address address = Address.fromKey(NetworkAddressType.MAIN_PKH, PublicKey.fromBytes(first.getPubKey()));

Utils.WHITESPACE_SPLITTER splits a phrase on any run of whitespace, which is more forgiving of user input than String.split(" "). It is a good idea to lower-case the input as well, since the word list is in lower case.