1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 | 'use strict'
|
18 |
|
19 | import { Bw } from './bw'
|
20 | import { Hash } from './hash'
|
21 | import pbkdf2 from 'pbkdf2-compat'
|
22 | import { Random } from './random'
|
23 | import { Struct } from './struct'
|
24 | import { wordList } from './bip-39-en-wordlist'
|
25 | import { Workers } from './workers'
|
26 |
|
27 | class 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 |
|
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 |
|
156 |
|
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 |
|
197 |
|
198 |
|
199 | check () {
|
200 | const mnemonic = this.mnemonic
|
201 |
|
202 |
|
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 |
|
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 |
|
237 |
|
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 |
|
275 | export { Bip39 }
|