]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
mnemonic.normalizeString method made public
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / index.js
CommitLineData
ebd8d4e8
IC
1(function() {
2
3 var mnemonic = new Mnemonic("english");
4 var bip32RootKey = null;
5 var bip32ExtendedKey = null;
6 var network = Bitcoin.networks.bitcoin;
7 var addressRowTemplate = $("#address-row-template");
8
9 var phraseChangeTimeoutEvent = null;
10
11 var DOM = {};
12 DOM.phrase = $(".phrase");
1abcc511 13 DOM.passphrase = $(".passphrase");
ebd8d4e8
IC
14 DOM.generate = $(".generate");
15 DOM.rootKey = $(".root-key");
16 DOM.extendedPrivKey = $(".extended-priv-key");
17 DOM.extendedPubKey = $(".extended-pub-key");
18 DOM.bip32path = $("#bip32-path");
19 DOM.bip44path = $("#bip44-path");
20 DOM.bip44purpose = $("#bip44 .purpose");
21 DOM.bip44coin = $("#bip44 .coin");
22 DOM.bip44account = $("#bip44 .account");
23 DOM.bip44change = $("#bip44 .change");
24 DOM.strength = $(".strength");
25 DOM.addresses = $(".addresses");
26 DOM.rowsToAdd = $(".rows-to-add");
27 DOM.more = $(".more");
28 DOM.feedback = $(".feedback");
29 DOM.tab = $(".derivation-type a");
30 DOM.indexToggle = $(".index-toggle");
31 DOM.addressToggle = $(".address-toggle");
32 DOM.privateKeyToggle = $(".private-key-toggle");
33
34 var derivationPath = DOM.bip44path.val();
35
36 function init() {
37 // Events
a19a5498
IC
38 DOM.phrase.on("input", delayedPhraseChanged);
39 DOM.passphrase.on("input", delayedPhraseChanged);
ebd8d4e8
IC
40 DOM.generate.on("click", generateClicked);
41 DOM.more.on("click", showMore);
a19a5498
IC
42 DOM.bip32path.on("input", bip32Changed);
43 DOM.bip44purpose.on("input", bip44Changed);
44 DOM.bip44coin.on("input", bip44Changed);
45 DOM.bip44account.on("input", bip44Changed);
46 DOM.bip44change.on("input", bip44Changed);
ebd8d4e8
IC
47 DOM.tab.on("click", tabClicked);
48 DOM.indexToggle.on("click", toggleIndexes);
49 DOM.addressToggle.on("click", toggleAddresses);
50 DOM.privateKeyToggle.on("click", togglePrivateKeys);
51 disableForms();
52 hidePending();
53 hideValidationError();
54 }
55
56 // Event handlers
57
58 function delayedPhraseChanged() {
59 hideValidationError();
60 showPending();
61 if (phraseChangeTimeoutEvent != null) {
62 clearTimeout(phraseChangeTimeoutEvent);
63 }
64 phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
65 }
66
67 function phraseChanged() {
68 showPending();
69 hideValidationError();
70 // Get the mnemonic phrase
71 var phrase = DOM.phrase.val();
1abcc511 72 var passphrase = DOM.passphrase.val();
ebd8d4e8
IC
73 var errorText = findPhraseErrors(phrase);
74 if (errorText) {
75 showValidationError(errorText);
76 return;
77 }
78 // Get the derivation path
79 var errorText = findDerivationPathErrors();
80 if (errorText) {
81 showValidationError(errorText);
82 return;
83 }
84 // Calculate and display
1abcc511 85 calcBip32Seed(phrase, passphrase, derivationPath);
ebd8d4e8
IC
86 displayBip32Info();
87 hidePending();
88 }
89
90 function generateClicked() {
91 clearDisplay();
92 showPending();
93 setTimeout(function() {
94 var phrase = generateRandomPhrase();
95 if (!phrase) {
96 return;
97 }
98 phraseChanged();
99 }, 50);
100 }
101
102 function tabClicked(e) {
103 var activePath = $(e.target.getAttribute("href") + " .path");
104 derivationPath = activePath.val();
105 derivationChanged();
106 }
107
108 function derivationChanged() {
59780293 109 delayedPhraseChanged();
ebd8d4e8
IC
110 }
111
112 function bip32Changed() {
113 derivationPath = DOM.bip32path.val();
114 derivationChanged();
115 }
116
117 function bip44Changed() {
118 setBip44DerivationPath();
119 derivationPath = DOM.bip44path.val();
120 derivationChanged();
121 }
122
123 function toggleIndexes() {
124 $("td.index span").toggleClass("invisible");
125 }
126
127 function toggleAddresses() {
128 $("td.address span").toggleClass("invisible");
129 }
130
131 function togglePrivateKeys() {
132 $("td.privkey span").toggleClass("invisible");
133 }
134
135 // Private methods
136
137 function generateRandomPhrase() {
138 if (!hasStrongRandom()) {
139 var errorText = "This browser does not support strong randomness";
140 showValidationError(errorText);
141 return;
142 }
143 var numWords = parseInt(DOM.strength.val());
144 // Check strength is an integer
145 if (isNaN(numWords)) {
146 DOM.strength.val("12");
147 numWords = 12;
148 }
149 // Check strength is a multiple of 32, if not round it down
150 if (numWords % 3 != 0) {
151 numWords = Math.floor(numWords / 3) * 3;
152 DOM.strength.val(numWords);
153 }
154 // Check strength is at least 32
155 if (numWords == 0) {
156 numWords = 3;
157 DOM.strength.val(numWords);
158 }
159 var strength = numWords / 3 * 32;
160 var words = mnemonic.generate(strength);
161 DOM.phrase.val(words);
162 return words;
163 }
164
1abcc511
PR
165 function calcBip32Seed(phrase, passphrase, path) {
166 var seed = mnemonic.toSeed(phrase, passphrase);
bade1504 167 bip32RootKey = Bitcoin.HDNode.fromSeedHex(seed, network);
ebd8d4e8
IC
168 bip32ExtendedKey = bip32RootKey;
169 // Derive the key from the path
170 var pathBits = path.split("/");
171 for (var i=0; i<pathBits.length; i++) {
172 var bit = pathBits[i];
173 var index = parseInt(bit);
174 if (isNaN(index)) {
175 continue;
176 }
177 var hardened = bit[bit.length-1] == "'";
178 if (hardened) {
179 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
180 }
181 else {
182 bip32ExtendedKey = bip32ExtendedKey.derive(index);
183 }
184 }
185 }
186
187 function showValidationError(errorText) {
188 DOM.feedback
189 .text(errorText)
190 .show();
191 }
192
193 function hideValidationError() {
194 DOM.feedback
195 .text("")
196 .hide();
197 }
198
199 function findPhraseErrors(phrase) {
200 // TODO make this right
201 // Preprocess the words
202 var parts = phrase.split(" ");
203 var proper = [];
204 for (var i=0; i<parts.length; i++) {
205 var part = parts[i];
206 if (part.length > 0) {
207 // TODO check that lowercasing is always valid to do
208 proper.push(part.toLowerCase());
209 }
210 }
211 // TODO some levenstein on the words
212 var properPhrase = proper.join(' ');
213 // Check the words are valid
214 var isValid = mnemonic.check(properPhrase);
215 if (!isValid) {
216 return "Invalid mnemonic";
217 }
218 return false;
219 }
220
221 function findDerivationPathErrors(path) {
222 // TODO
223 return false;
224 }
225
226 function displayBip32Info() {
227 // Display the key
228 var rootKey = bip32RootKey.toBase58();
229 DOM.rootKey.val(rootKey);
230 var extendedPrivKey = bip32ExtendedKey.toBase58();
231 DOM.extendedPrivKey.val(extendedPrivKey);
232 var extendedPubKey = bip32ExtendedKey.toBase58(false);
233 DOM.extendedPubKey.val(extendedPubKey);
234 // Display the addresses and privkeys
235 clearAddressesList();
236 displayAddresses(0, 20);
237 }
238
239 function displayAddresses(start, total) {
240 for (var i=0; i<total; i++) {
241 var index = i+ start;
242 var key = bip32ExtendedKey.derive(index);
243 var address = key.getAddress().toString();
ec1e50df 244 var privkey = key.privKey.toWIF(network);
ebd8d4e8
IC
245 addAddressToList(index, address, privkey);
246 }
247 }
248
249 function showMore() {
250 var start = DOM.addresses.children().length;
251 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
252 if (isNaN(rowsToAdd)) {
253 rowsToAdd = 20;
254 DOM.rowsToAdd.val("20");
255 }
256 if (rowsToAdd > 200) {
257 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
258 msg += "Do you want to continue?";
259 if (!confirm(msg)) {
260 return;
261 }
262 }
263 showPending();
264 setTimeout(function() {
265 displayAddresses(start, rowsToAdd);
266 hidePending();
267 }, 50);
268 }
269
270 function clearDisplay() {
271 clearAddressesList();
272 clearKey();
273 hideValidationError();
274 }
275
276 function clearAddressesList() {
277 DOM.addresses.empty();
278 }
279
280 function clearKey() {
281 DOM.rootKey.val("");
282 DOM.extendedPrivKey.val("");
283 DOM.extendedPubKey.val("");
284 }
285
286 function addAddressToList(index, address, privkey) {
287 var row = $(addressRowTemplate.html());
288 row.find(".index span").text(index);
289 row.find(".address span").text(address);
290 row.find(".privkey span").text(privkey);
291 DOM.addresses.append(row);
292 }
293
294 function hasStrongRandom() {
295 return 'crypto' in window && window['crypto'] !== null;
296 }
297
298 function disableForms() {
299 $("form").on("submit", function(e) {
300 e.preventDefault();
301 });
302 }
303
304 function setBip44DerivationPath() {
305 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
306 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
307 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
308 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
309 var path = "m/";
310 path += purpose + "'/";
311 path += coin + "'/";
312 path += account + "'/";
313 path += change;
314 DOM.bip44path.val(path);
315 }
316
317 function parseIntNoNaN(val, defaultVal) {
318 var v = parseInt(val);
319 if (isNaN(v)) {
320 return defaultVal;
321 }
322 return v;
323 }
324
325 function showPending() {
326 DOM.feedback
327 .text("Calculating...")
328 .show();
329 }
330
331 function hidePending() {
332 DOM.feedback
333 .text("")
334 .hide();
335 }
336
337 init();
338
339})();