Open-source project
bitcoinjs/bip39 avatar
bitcoinjs/bip39

bip39 turns entropy into a word list, and back again

JavaScript implementation of Bitcoin BIP39: Mnemonic code for generating deterministic keys

1,183 stars461 forksJavaScriptISC

At a glance

What is it?
The bip39 package is a JavaScript implementation of the Bitcoin BIP39 mnemonic standard, exposing generation, seed derivation and validation with ten wordlists. Its most useful feature for wallet developers is not the encoding but the guidance on recovering phrases that fail a checksum.
Who is it for?
bip39 is the right dependency for a wallet, a key management service or any tool that has to show a user a recoverable secret phrase and later read it back, because it implements the standard, ships every wordlist and tells you how to handle a phrase that does not validate. It is the wrong tool for storing a phrase, for generating keys, and for anyone who needs an audited cryptographic primitive, since the hashing and randomness come from other packages.
Can I use it commercially?
Yes. ISC is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 37 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

What BIP39 specifies, and where this package stops

BIP39 is a specification, and this repository is its JavaScript implementation: mnemonic code for generating deterministic keys. The mechanism it implements is a reversible encoding of random entropy into a sequence of words from a fixed list, with a checksum appended so that a typo can be detected.

The package description calls it mnemonic code for generating deterministic keys, and it is worth being precise about where the responsibility ends. Looking at the exported functions, the library takes entropy and turns it into words, takes words and turns them into a seed, and validates a phrase against a checksum. It does not derive addresses, sign transactions or manage keys. The seed it produces is the boundary of the library, and everything a wallet does with that seed belongs to another layer.

That scope is a virtue for a package like this. The standard is small enough to implement in a few hundred lines, and getting it subtly wrong is a serious matter, since a phrase that another wallet cannot read is a lost wallet. Using a package that other projects also depend on means the encoding is the same everywhere, which is the property that makes a phrase portable between applications at all.

The audience is therefore wallet developers and anyone building a key management flow in JavaScript or TypeScript, on the server or in a browser, where a user has to be shown words and later type them back.

The API is small and each function has one job

The exported surface is short enough to learn in a minute. generateMnemonic creates a random phrase and uses crypto.randomBytes under the hood, defaulting to 128 bits of entropy. entropyToMnemonic encodes entropy given as a hex string. mnemonicToEntropy reverses that, so the round trip is exact, and mnemonicToEntropy is documented as reversible in the examples.

Seed derivation comes in two forms. mnemonicToSeedSync returns a Buffer directly and the documentation notes that it is less performance oriented, while mnemonicToSeed returns a promise for the same work. Both accept an optional passphrase, shown in the examples as a second argument, which is the BIP39 passphrase that mixes additional words into the seed.

Validation is validateMnemonic, which returns true or false. In the examples, a generated phrase validates and a short made up phrase does not.

The wordlist handling is the part that needs a little more attention. The wordlists object exposes the lists by language, and the name of the JSON file minus its extension is the key, so english, japanese, spanish, italian, french, korean, czech, portuguese, chinese_simplified and chinese_traditional are the ten that ship. The default wordlist is English, and setDefaultWordlist changes it at runtime for every subsequent call that does not pass an explicit list.

The practical advice for a wallet is to pass the wordlist explicitly on every call rather than relying on the default, because a global default that another library in the same process changes is a subtle way to generate a phrase in the wrong language.

Installing the package and generating a first phrase

The installation is a single command from npm:

bash
npm install bip39

The package is published as bip39, and the published artefact is version 3.1.0 with the entry point at src/index.js and type declarations at./types/index.d.ts. The runtime dependency list is short, with @noble/hashes at version 1.2.0 or newer providing the hashing primitives, so there is no large tree to audit.

A first script looks like this:

js
const bip39 = require('bip39')

// defaults to BIP39 English word list
// uses HEX strings for entropy
const mnemonic = bip39.entropyToMnemonic('00000000000000000000000000000000')
// => abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about

// reversible
bip39.mnemonicToEntropy(mnemonic)
// => '00000000000000000000000000000000'

For a real phrase rather than the all zero test vector, generateMnemonic does the work, and you should check the result before showing it to a user:

js
const mnemonic = bip39.generateMnemonic()
bip39.validateMnemonic(mnemonic)
// => true

Two details are worth keeping in mind when you write the surrounding application. The entropy input is a hex string, so a caller that has raw bytes has to convert them, and the number of words changes with the entropy size, so do not hard code twelve. And the phrase is a secret. The examples in the documentation print phrases and seeds to the console because they are test vectors, and copying that pattern into a log statement in production is how a secret ends up somewhere it should not be.

Ten wordlists are a heavy thing to put in a browser bundle

The wordlists are the bulk of this package, and the documentation has a dedicated section on removing them from a browserify or webpack bundle. Bundles can get very large if all the wordlists are included, so the package lets you exclude them. The browserify example excludes every list except chinese_simplified:

bash
$ browserify -r bip39 -s bip39 \
  --exclude=./wordlists/english.json \
  --exclude=./wordlists/japanese.json \
  --exclude=./wordlists/spanish.json \
  --exclude=./wordlists/italian.json \
  --exclude=./wordlists/french.json \
  --exclude=./wordlists/korean.json \
  --exclude=./wordlists/czech.json \
  --exclude=./wordlists/portuguese.json \
  --exclude=./wordlists/chinese_traditional.json \
   > bip39.browser.js

The result is a bundle that contains only the chinese_simplified wordlist, and that list becomes the default for every call that does not pass one explicitly. The webpack 5 equivalent uses the IgnorePlugin with a checkResource pattern that lets only the English list through, and the documentation shows the console result of a build made that way, with every other wordlist reporting as undefined.

That last detail has a consequence for application code. If a user holds a phrase in Spanish and your bundle excluded the Spanish list, validateMnemonic cannot check it, and neither can you offer recovery in that language. The trade-off is straightforward: exclude the lists you do not serve, but make sure the ones you serve are present, and handle the case where a list is missing rather than letting the call fail with an undefined.

The recovery rule: warn on an invalid checksum, do not reject

The most valuable part of the documentation for a wallet developer is the reminder for developers at the top of the README, and it is not about encoding. It asks you to remember to allow recovery from mnemonic phrases that have invalid checksums, or that you do not have the wordlist for. When a checksum is invalid, warn the user that the phrase is not something generated by your app, and ask if they would like to use it anyway. The stated reason is portability: your app only needs to hold the wordlists for the languages you support, but it can still recover phrases made by other apps in other languages.

The suggestion is to add other checks, such as making sure the user is entering twelve words or more separated by a space, with the example phrase.trim().split(/\s+/g).length >= 12.

This is a design decision with a real failure mode behind it, and it is worth stating plainly. A strict implementation that refuses any phrase failing its checksum will lock a user out of funds that a lenient implementation would have recovered, because a single mistyped word, a wrong wordlist or a phrase written down with an unusual separator all produce a checksum failure even though the underlying seed is recoverable. The checksum exists to catch typos, not to authenticate the phrase.

The counter-argument is real too. Accepting invalid phrases means your app will derive a seed from a phrase the user may have mistyped, and the derived addresses will be empty. So the wording of the warning matters, and the check for a plausible word count is a cheap filter in front of it.

A TypeScript build with tape tests and a tight coverage gate

The repository layout shows a small project with real engineering discipline. There is a src directory holding the built output, a ts_src directory with the TypeScript sources, a types directory with the declaration files, a util directory, and a test directory. The build script runs the clean step and then tsc against tsconfig.json, and the published files list is just src and types, so consumers get the compiled JavaScript and the type declarations rather than the sources.

The test and lint setup is in the package scripts. The unit tests run with tape over test/*.js, the format check runs prettier in check mode over the TypeScript sources, and lint runs tslint with the project configuration. The test script chains all of it, build, format check, lint and unit, so a single npm test runs the whole gate. There is also a coverage command using nyc with thresholds of 85 for branches and 100 for functions, which is a strict bar for a package of this size, and a gitdiff:ci script that rebuilds and fails if the committed output differs from the build, which keeps the published src directory honest.

The wordlist update script is separate: node -e "require('./util/wordlists').update()" refreshes the lists in the wordlists folder, which matters because a new language list is a change to the standard rather than a change to the code.

The licence is ISC, as stated in the package metadata and in the LICENSE file. ISC is a permissive licence with a short grant, which for a library that ends up inside wallets and custodial services is about as light a licensing obligation as a dependency can carry.

What this package deliberately does not do

The clearest limits come from the API surface and from what the documentation is silent about. The library produces a seed and does not store it, encrypt it, back it up or display it in a safe way. It has no notion of a wallet, an account, a keystore or a user interface, so every one of those decisions stays with the application, and the phrase handling rules in the developer reminder are the only guidance offered about how to treat a secret.

The documentation also does not describe a passphrase policy, a user flow for confirming a phrase, or any way to validate a phrase against a known wallet. It does not state a recovery process beyond the checksum warning, and it does not discuss what to do when a user has words in a language your bundle excluded.

The alternative to using this package is to implement the encoding yourself, and the reason not to is not effort but interoperability. BIP39 is a byte level contract between wallets: a phrase produced by one application has to be readable by another, and the checksum and the wordlist ordering are what make that work. Writing a quick encoder gives you a phrase that your own application can read and nobody else's, which is the same as having no standard at all.

The other alternative is to stop at the seed and use a different scheme entirely, if your use case does not need a human readable phrase. If the secret never has to be typed by a person, a random key from crypto.randomBytes and your own storage is simpler and has fewer failure modes than a 24 word phrase. bip39 earns its place exactly when a human has to be the one who carries the secret.

Editorial conclusion

bip39 is the right dependency for a wallet, a key management service or any tool that has to show a user a recoverable secret phrase and later read it back, because it implements the standard, ships every wordlist and tells you how to handle a phrase that does not validate. It is the wrong tool for storing a phrase, for generating keys, and for anyone who needs an audited cryptographic primitive, since the hashing and randomness come from other packages. Before you adopt it, read the developer reminder in the README and make your recovery flow warn rather than reject, confirm that the wordlist your users hold is one of the ten in the wordlists folder, and note that the published package is version 3.1.0 while the repository itself was pushed to on 2026-09-16 with no GitHub releases to track it.

Frequently asked questions

How do I install bip39 and generate a mnemonic phrase?

Run npm install bip39, then require the package and call bip39.generateMnemonic(), which uses crypto.randomBytes under the hood and defaults to 128 bits of entropy. You can check the result with bip39.validateMnemonic, which returns true for a valid phrase.

What is the difference between mnemonicToSeed and mnemonicToSeedSync?

mnemonicToSeed returns a promise and the documentation notes that mnemonicToSeedSync is less performance oriented. Both accept an optional passphrase as a second argument, and both produce the same seed bytes.

How should a wallet handle a mnemonic phrase that fails validation?

The developer reminder in the README asks you to allow recovery from phrases with an invalid checksum or an unknown wordlist, warn the user that the phrase was not generated by your app, and ask whether they want to use it anyway. It also suggests a word count check, phrase.trim().split(/\s+/g).length >= 12.

How do I keep the bip39 wordlists out of a browser bundle?

The README shows a browserify command with --exclude=./wordlists/english.json and the other lists, and a webpack 5 example using the IgnorePlugin with a checkResource pattern. A bundle built this way contains only the wordlists you keep, and the excluded ones report as undefined.

Does bip39 generate wallet addresses or store my keys?

No. The package implements the mnemonic code and derives a seed from a phrase, and the project description frames it as mnemonic code for generating deterministic keys. Key derivation from that seed, storage, encryption and any user interface are separate concerns that the library does not handle.

Official sources

  1. bitcoinjs/bip39 on GitHub
  2. Issues
  3. License: ISC
  4. README
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/bitcoinjs-bip39.svg)](https://hysenlabs.com/projects/bitcoinjs-bip39)

Was this useful?