UNPKG

7.05 kBJavaScriptView Raw
1/**
2 * Bip39: Mnemonic Seeds
3 * =====================
4 *
5 * Bip39 is a way to turn random entropy into a mnemonic (a string of words
6 * from a wordlist), and then that mnemonic into a seed. The seed can then be
7 * used in Bip32 to derive hierarchical deterministic keys. It does not go the
8 * other way around (i.e., you cannot turn a seed into a mnemonic). The usual
9 * way to use it is either to generate a new one, like this:
10 *
11 * const mnemonic = new Bip39().fromRandom().toString()
12 *
13 * or from a known mnemonic:
14 *
15 * const seed = new Bip39().fromString(mnemonic).toSeed()
16 */
17'use strict'
18
19import { Bw } from './bw'
20import { Hash } from './hash'
21import pbkdf2 from 'pbkdf2-compat'
22import { Random } from './random'
23import { Struct } from './struct'
24import { wordList } from './bip-39-en-wordlist'
25import { Workers } from './workers'
26
27class Bip39 extends Struct {
28 constructor (mnemonic, seed, wordlist = wordList) {
29 super({ mnemonic, seed })
30 this.Wordlist = wordlist
31 }
32
33 toBw (bw) {
34 if (!bw) {
35 bw = new Bw()
36 }
37 if (this.mnemonic) {
38 const buf = Buffer.from(this.mnemonic)
39 bw.writeVarIntNum(buf.length)
40 bw.write(buf)
41 } else {
42 bw.writeVarIntNum(0)
43 }
44 if (this.seed) {
45 bw.writeVarIntNum(this.seed.length)
46 bw.write(this.seed)
47 } else {
48 bw.writeVarIntNum(0)
49 }
50 return bw
51 }
52
53 fromBr (br) {
54 const mnemoniclen = br.readVarIntNum()
55 if (mnemoniclen > 0) {
56 this.mnemonic = br.read(mnemoniclen).toString()
57 }
58 const seedlen = br.readVarIntNum()
59 if (seedlen > 0) {
60 this.seed = br.read(seedlen)
61 }
62 return this
63 }
64
65 /**
66 * Generate a random new mnemonic from the wordlist.
67 */
68 fromRandom (bits) {
69 if (!bits) {
70 bits = 128
71 }
72 if (bits % 32 !== 0) {
73 throw new Error('bits must be multiple of 32')
74 }
75 if (bits < 128) {
76 throw new Error('bits must be at least 128')
77 }
78 const buf = Random.getRandomBuffer(bits / 8)
79 this.entropy2Mnemonic(buf)
80 this.mnemonic2Seed()
81 return this
82 }
83
84 static fromRandom (bits) {
85 return new this().fromRandom(bits)
86 }
87
88 async asyncFromRandom (bits) {
89 if (!bits) {
90 bits = 128
91 }
92 const buf = Random.getRandomBuffer(bits / 8)
93 let workersResult = await Workers.asyncObjectMethod(
94 this,
95 'entropy2Mnemonic',
96 [buf]
97 )
98 const bip39 = new Bip39().fromFastBuffer(workersResult.resbuf)
99 workersResult = await Workers.asyncObjectMethod(
100 bip39,
101 'mnemonic2Seed',
102 []
103 )
104 return this.fromFastBuffer(workersResult.resbuf)
105 }
106
107 static asyncFromRandom (bits) {
108 return new this().asyncFromRandom(bits)
109 }
110
111 fromEntropy (buf) {
112 this.entropy2Mnemonic(buf)
113 return this
114 }
115
116 static fromEntropy (buf) {
117 return new this().fromEntropy(buf)
118 }
119
120 async asyncFromEntropy (buf) {
121 const workersResult = await Workers.asyncObjectMethod(this, 'fromEntropy', [
122 buf
123 ])
124 return this.fromFastBuffer(workersResult.resbuf)
125 }
126
127 static asyncFromEntropy (buf) {
128 return new this().asyncFromEntropy(buf)
129 }
130
131 fromString (mnemonic) {
132 this.mnemonic = mnemonic
133 return this
134 }
135
136 toString () {
137 return this.mnemonic
138 }
139
140 toSeed (passphrase) {
141 this.mnemonic2Seed(passphrase)
142 return this.seed
143 }
144
145 async asyncToSeed (passphrase) {
146 if (passphrase === undefined) {
147 passphrase = ''
148 }
149 const args = [passphrase]
150 const workersResult = await Workers.asyncObjectMethod(this, 'toSeed', args)
151 return workersResult.resbuf
152 }
153
154 /**
155 * Generate a new mnemonic from some entropy generated somewhere else. The
156 * entropy must be at least 128 bits.
157 */
158 entropy2Mnemonic (buf) {
159 if (!Buffer.isBuffer(buf) || buf.length < 128 / 8) {
160 throw new Error(
161 'Entropy is less than 128 bits. It must be 128 bits or more.'
162 )
163 }
164
165 const hash = Hash.sha256(buf)
166 let bin = ''
167 const bits = buf.length * 8
168 for (let i = 0; i < buf.length; i++) {
169 bin = bin + ('00000000' + buf[i].toString(2)).slice(-8)
170 }
171 let hashbits = hash[0].toString(2)
172 hashbits = ('00000000' + hashbits).slice(-8).slice(0, bits / 32)
173 bin = bin + hashbits
174
175 if (bin.length % 11 !== 0) {
176 throw new Error(
177 'internal error - entropy not an even multiple of 11 bits - ' +
178 bin.length
179 )
180 }
181
182 let mnemonic = ''
183 for (let i = 0; i < bin.length / 11; i++) {
184 if (mnemonic !== '') {
185 mnemonic = mnemonic + this.Wordlist.space
186 }
187 const wi = parseInt(bin.slice(i * 11, (i + 1) * 11), 2)
188 mnemonic = mnemonic + this.Wordlist[wi]
189 }
190
191 this.mnemonic = mnemonic
192 return this
193 }
194
195 /**
196 * Check that a mnemonic is valid. This means there should be no superfluous
197 * whitespace, no invalid words, and the checksum should match.
198 */
199 check () {
200 const mnemonic = this.mnemonic
201
202 // confirm no invalid words
203 const words = mnemonic.split(this.Wordlist.space)
204 let bin = ''
205 for (let i = 0; i < words.length; i++) {
206 const ind = this.Wordlist.indexOf(words[i])
207 if (ind < 0) {
208 return false
209 }
210 bin = bin + ('00000000000' + ind.toString(2)).slice(-11)
211 }
212
213 if (bin.length % 11 !== 0) {
214 throw new Error(
215 'internal error - entropy not an even multiple of 11 bits - ' +
216 bin.length
217 )
218 }
219
220 // confirm checksum
221 const cs = bin.length / 33
222 const hashBits = bin.slice(-cs)
223 const nonhashBits = bin.slice(0, bin.length - cs)
224 const buf = Buffer.alloc(nonhashBits.length / 8)
225 for (let i = 0; i < nonhashBits.length / 8; i++) {
226 buf.writeUInt8(parseInt(bin.slice(i * 8, (i + 1) * 8), 2), i)
227 }
228 const hash = Hash.sha256(buf)
229 let expectedHashBits = hash[0].toString(2)
230 expectedHashBits = ('00000000' + expectedHashBits).slice(-8).slice(0, cs)
231
232 return expectedHashBits === hashBits
233 }
234
235 /**
236 * Convert a mnemonic to a seed. Does not check for validity of the mnemonic -
237 * for that, you should manually run check() first.
238 */
239 mnemonic2Seed (passphrase = '') {
240 let mnemonic = this.mnemonic
241 if (!this.check()) {
242 throw new Error(
243 'Mnemonic does not pass the check - was the mnemonic typed incorrectly? Are there extra spaces?'
244 )
245 }
246 if (typeof passphrase !== 'string') {
247 throw new Error('passphrase must be a string or undefined')
248 }
249 mnemonic = mnemonic.normalize('NFKD')
250 passphrase = passphrase.normalize('NFKD')
251 const mbuf = Buffer.from(mnemonic)
252 const pbuf = Buffer.concat([
253 Buffer.from('mnemonic'),
254 Buffer.from(passphrase)
255 ])
256 this.seed = pbkdf2.pbkdf2Sync(mbuf, pbuf, 2048, 64, 'sha512')
257 return this
258 }
259
260 isValid (passphrase = '') {
261 let isValid
262 try {
263 isValid = !!this.mnemonic2Seed(passphrase)
264 } catch (err) {
265 isValid = false
266 }
267 return isValid
268 }
269
270 static isValid (mnemonic, passphrase = '') {
271 return new Bip39(mnemonic).isValid(passphrase)
272 }
273}
274
275export { Bip39 }