bip39.mx raw
1 // Package bip39 implements BIP-39 mnemonic encoding, validation, and seed derivation.
2 // https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki
3 //
4 // Currently supports 12-word mnemonics (128-bit entropy + 4-bit checksum).
5 // Wordlist: English only (Wordlist.English).
6 package bip39
7
8 import (
9 "crypto/pbkdf2"
10 "crypto/sha256"
11 "crypto/sha512"
12 )
13
14 // EntropyToMnemonic converts 16 bytes (128 bits) of entropy into a 12-word
15 // English BIP-39 mnemonic. Returns "" on bad input length.
16 func EntropyToMnemonic(entropy []byte) string {
17 if len(entropy) != 16 {
18 return ""
19 }
20 hash := sha256.Sum(entropy)
21 all := []byte{:17}
22 copy(all, entropy)
23 all[16] = hash[0]
24 eng := English()
25 buf := []byte{:0}
26 for i := 0; i < 12; i++ {
27 bitPos := i * 11
28 byteIdx := bitPos / 8
29 bitOff := uint(bitPos % 8)
30 var val uint32
31 val = uint32(all[byteIdx]) << 16
32 if byteIdx+1 < 17 {
33 val |= uint32(all[byteIdx+1]) << 8
34 }
35 if byteIdx+2 < 17 {
36 val |= uint32(all[byteIdx+2])
37 }
38 idx := (val << bitOff >> 13) & 0x7FF
39 if i > 0 {
40 buf = append(buf, ' ')
41 }
42 buf = append(buf, eng[idx]...)
43 }
44 return string(buf)
45 }
46
47 // ValidateMnemonic returns true if phrase is a valid 12-word BIP-39 English
48 // mnemonic with a correct checksum.
49 func ValidateMnemonic(phrase string) bool {
50 if phrase == "" {
51 return false
52 }
53 var words [13]string
54 n := 0
55 start := 0
56 for i := 0; i <= len(phrase); i++ {
57 if i == len(phrase) || phrase[i] == ' ' {
58 if i > start {
59 if n >= 13 {
60 return false
61 }
62 words[n] = phrase[start:i]
63 n++
64 }
65 start = i + 1
66 }
67 }
68 if n != 12 {
69 return false
70 }
71 eng := English()
72 var indices [12]uint32
73 for w := 0; w < 12; w++ {
74 found := false
75 for i := 0; i < 2048; i++ {
76 if eng[i] == words[w] {
77 indices[w] = uint32(i)
78 found = true
79 break
80 }
81 }
82 if !found {
83 return false
84 }
85 }
86 // Reconstruct 132-bit stream into all[17]: 128 entropy + 4 checksum.
87 var all [17]byte
88 for w := 0; w < 12; w++ {
89 idx := indices[w]
90 for bit := 0; bit < 11; bit++ {
91 if (idx>>uint(10-bit))&1 != 0 {
92 streamPos := w*11 + bit
93 all[streamPos/8] |= 1 << uint(7-(streamPos%8))
94 }
95 }
96 }
97 entropy := all[:16]
98 cs := all[16] >> 4
99 hash := sha256.Sum(entropy)
100 return (hash[0] >> 4) == cs
101 }
102
103 // MnemonicToSeed derives a 64-byte seed from a mnemonic + optional passphrase
104 // per BIP-39 ("mnemonic" || passphrase as salt, 2048 PBKDF2-HMAC-SHA512 rounds).
105 // Returns nil on PBKDF2 failure (should never happen with valid params).
106 func MnemonicToSeed(phrase, passphrase string) []byte {
107 salt := []byte{:0:8+len(passphrase)}
108 salt = append(salt, 'm', 'n', 'e', 'm', 'o', 'n', 'i', 'c')
109 salt = append(salt, passphrase...)
110 dk, err := pbkdf2.Key(sha512.New, []byte(phrase), salt, 2048, 64)
111 if err != nil {
112 return nil
113 }
114 return dk
115 }
116