// Package bip39 implements BIP-39 mnemonic generation, validation and seed // derivation for the English wordlist. It builds on crypto/pbkdf2 and an // embedded copy of the canonical 2048-word list. package bip39 import ( "crypto/pbkdf2" "crypto/rand" "crypto/sha256" "crypto/sha512" _ "embed" "errors" "strings" ) //go:embed english.txt var data string // ErrInvalidMnemonic is returned for malformed or checksum-failing phrases. var ErrInvalidMnemonic = errors.New("bip39: invalid mnemonic") // ErrInvalidWordsCount is returned for unsupported word counts. var ErrInvalidWordsCount = errors.New("bip39: invalid words count") var words []string var wordIndex map[string]int func init() { words = strings.Fields(data) if len(words) != 2048 { panic("bip39: english wordlist must contain 2048 words") } wordIndex = make(map[string]int, len(words)) for i, w := range words { wordIndex[w] = i } } // Words returns the embedded wordlist. func Words() []string { return words } // ValidWordCount reports whether n is a supported BIP-39 word count. func ValidWordCount(n int) bool { _, ok := entropyBits[n] return ok } // entropyBits maps a supported word count to its entropy size in bits. var entropyBits = map[int]int{12: 128, 15: 160, 18: 192, 21: 224, 24: 256} // NewMnemonic generates a mnemonic with the given number of words using // cryptographically secure entropy. Supported counts are 12, 15, 18, 21, 24. func NewMnemonic(numWords int) (string, error) { bits, ok := entropyBits[numWords] if !ok { return "", ErrInvalidWordsCount } entropy := make([]byte, bits/8) if _, err := rand.Read(entropy); err != nil { return "", err } return entropyToMnemonic(entropy) } // entropyToMnemonic encodes entropy with its SHA-256 checksum into words. func entropyToMnemonic(entropy []byte) (string, error) { cs := sha256.Sum256(entropy) csBits := len(entropy) * 8 / 32 bits := make([]bool, 0, len(entropy)*8+csBits) for _, b := range entropy { for i := 7; i >= 0; i-- { bits = append(bits, (b>>i)&1 == 1) } } for i := 0; i < csBits; i++ { bits = append(bits, (cs[i/8]>>(7-uint(i)%8))&1 == 1) } out := make([]string, 0, (len(entropy)*8+csBits)/11) for i := 0; i+11 <= len(bits); i += 11 { idx := 0 for j := 0; j < 11; j++ { idx = idx<<1 | boolToInt(bits[i+j]) } out = append(out, words[idx]) } return strings.Join(out, " "), nil } func boolToInt(b bool) int { if b { return 1 } return 0 } // Validate reports whether the mnemonic is well-formed and checksum-correct. func Validate(mnemonic string) bool { _, ok := mnemonicToEntropy(mnemonic) return ok } // mnemonicToEntropy decodes a mnemonic back to entropy, verifying checksum. func mnemonicToEntropy(mnemonic string) ([]byte, bool) { fields := strings.Fields(mnemonic) wc := len(fields) if _, ok := entropyBits[wc]; !ok { return nil, false } totalBits := wc * 11 entropyLen := totalBits * 32 / 33 / 8 bits := make([]bool, 0, totalBits) for _, w := range fields { idx, ok := wordIndex[w] if !ok { return nil, false } for i := 10; i >= 0; i-- { bits = append(bits, (idx>>i)&1 == 1) } } entropy := make([]byte, entropyLen) for i := 0; i < entropyLen*8; i++ { if bits[i] { entropy[i/8] |= 1 << (7 - uint(i)%8) } } csBits := totalBits - entropyLen*8 cs := sha256.Sum256(entropy) for i := 0; i < csBits; i++ { if byte(boolToInt(bits[entropyLen*8+i])) != (cs[i/8]>>(7-uint(i)%8))&1 { return nil, false } } return entropy, true } // Seed derives the 64-byte BIP-39 seed from a mnemonic and passphrase using // PBKDF2-HMAC-SHA512 with 2048 iterations. func Seed(mnemonic, passphrase string) []byte { normalized := strings.Join(strings.Fields(mnemonic), " ") key, err := pbkdf2.Key(sha512.New, normalized, []byte("mnemonic"+passphrase), 2048, 64) if err != nil { panic(err) } return key }