]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - src/js/index.js
Display BIP39 seed
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / index.js
1 (function() {
2
3 var mnemonic = new Mnemonic("english");
4 var seed = null
5 var bip32RootKey = null;
6 var bip32ExtendedKey = null;
7 var network = bitcoin.networks.bitcoin;
8 var addressRowTemplate = $("#address-row-template");
9
10 var showIndex = true;
11 var showAddress = true;
12 var showPrivKey = true;
13
14 var phraseChangeTimeoutEvent = null;
15
16 var DOM = {};
17 DOM.network = $(".network");
18 DOM.phraseNetwork = $("#network-phrase");
19 DOM.phrase = $(".phrase");
20 DOM.passphrase = $(".passphrase");
21 DOM.generate = $(".generate");
22 DOM.seed = $(".seed");
23 DOM.rootKey = $(".root-key");
24 DOM.extendedPrivKey = $(".extended-priv-key");
25 DOM.extendedPubKey = $(".extended-pub-key");
26 DOM.bip32tab = $("#bip32-tab");
27 DOM.bip44tab = $("#bip44-tab");
28 DOM.bip32panel = $("#bip32");
29 DOM.bip44panel = $("#bip44");
30 DOM.bip32path = $("#bip32-path");
31 DOM.bip44path = $("#bip44-path");
32 DOM.bip44purpose = $("#bip44 .purpose");
33 DOM.bip44coin = $("#bip44 .coin");
34 DOM.bip44account = $("#bip44 .account");
35 DOM.bip44change = $("#bip44 .change");
36 DOM.strength = $(".strength");
37 DOM.addresses = $(".addresses");
38 DOM.rowsToAdd = $(".rows-to-add");
39 DOM.more = $(".more");
40 DOM.feedback = $(".feedback");
41 DOM.tab = $(".derivation-type a");
42 DOM.indexToggle = $(".index-toggle");
43 DOM.addressToggle = $(".address-toggle");
44 DOM.privateKeyToggle = $(".private-key-toggle");
45
46 var derivationPath = $(".tab-pane.active .path").val();
47
48 function init() {
49 // Events
50 DOM.network.on("change", networkChanged);
51 DOM.phrase.on("input", delayedPhraseChanged);
52 DOM.passphrase.on("input", delayedPhraseChanged);
53 DOM.generate.on("click", generateClicked);
54 DOM.more.on("click", showMore);
55 DOM.bip32path.on("input", bip32Changed);
56 DOM.bip44purpose.on("input", bip44Changed);
57 DOM.bip44coin.on("input", bip44Changed);
58 DOM.bip44account.on("input", bip44Changed);
59 DOM.bip44change.on("input", bip44Changed);
60 DOM.tab.on("click", tabClicked);
61 DOM.indexToggle.on("click", toggleIndexes);
62 DOM.addressToggle.on("click", toggleAddresses);
63 DOM.privateKeyToggle.on("click", togglePrivateKeys);
64 disableForms();
65 hidePending();
66 hideValidationError();
67 populateNetworkSelect();
68 }
69
70 // Event handlers
71
72 function networkChanged(e) {
73 var network = e.target.value;
74 networks[network].onSelect();
75 setBip44DerivationPath();
76 delayedPhraseChanged();
77 }
78
79 function delayedPhraseChanged() {
80 hideValidationError();
81 showPending();
82 if (phraseChangeTimeoutEvent != null) {
83 clearTimeout(phraseChangeTimeoutEvent);
84 }
85 phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
86 }
87
88 function phraseChanged() {
89 showPending();
90 hideValidationError();
91 // Get the mnemonic phrase
92 var phrase = DOM.phrase.val();
93 var passphrase = DOM.passphrase.val();
94 var errorText = findPhraseErrors(phrase);
95 if (errorText) {
96 showValidationError(errorText);
97 return;
98 }
99 // Get the derivation path
100 var errorText = findDerivationPathErrors();
101 if (errorText) {
102 showValidationError(errorText);
103 return;
104 }
105 // Calculate and display
106 calcBip32Seed(phrase, passphrase, derivationPath);
107 displayBip32Info();
108 hidePending();
109 }
110
111 function generateClicked() {
112 clearDisplay();
113 showPending();
114 setTimeout(function() {
115 var phrase = generateRandomPhrase();
116 if (!phrase) {
117 return;
118 }
119 phraseChanged();
120 }, 50);
121 }
122
123 function tabClicked(e) {
124 var activePath = $(e.target.getAttribute("href") + " .path");
125 derivationPath = activePath.val();
126 derivationChanged();
127 }
128
129 function derivationChanged() {
130 delayedPhraseChanged();
131 }
132
133 function bip32Changed() {
134 derivationPath = DOM.bip32path.val();
135 derivationChanged();
136 }
137
138 function bip44Changed() {
139 setBip44DerivationPath();
140 derivationChanged();
141 }
142
143 function toggleIndexes() {
144 showIndex = !showIndex;
145 $("td.index span").toggleClass("invisible");
146 }
147
148 function toggleAddresses() {
149 showAddress = !showAddress;
150 $("td.address span").toggleClass("invisible");
151 }
152
153 function togglePrivateKeys() {
154 showPrivKey = !showPrivKey;
155 $("td.privkey span").toggleClass("invisible");
156 }
157
158 // Private methods
159
160 function generateRandomPhrase() {
161 if (!hasStrongRandom()) {
162 var errorText = "This browser does not support strong randomness";
163 showValidationError(errorText);
164 return;
165 }
166 var numWords = parseInt(DOM.strength.val());
167 var strength = numWords / 3 * 32;
168 var words = mnemonic.generate(strength);
169 DOM.phrase.val(words);
170 return words;
171 }
172
173 function calcBip32Seed(phrase, passphrase, path) {
174 seed = mnemonic.toSeed(phrase, passphrase);
175 bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network);
176 bip32ExtendedKey = bip32RootKey;
177 // Derive the key from the path
178 var pathBits = path.split("/");
179 for (var i=0; i<pathBits.length; i++) {
180 var bit = pathBits[i];
181 var index = parseInt(bit);
182 if (isNaN(index)) {
183 continue;
184 }
185 var hardened = bit[bit.length-1] == "'";
186 if (hardened) {
187 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
188 }
189 else {
190 bip32ExtendedKey = bip32ExtendedKey.derive(index);
191 }
192 }
193 }
194
195 function showValidationError(errorText) {
196 DOM.feedback
197 .text(errorText)
198 .show();
199 }
200
201 function hideValidationError() {
202 DOM.feedback
203 .text("")
204 .hide();
205 }
206
207 function findPhraseErrors(phrase) {
208 // TODO make this right
209 // Preprocess the words
210 phrase = mnemonic.normalizeString(phrase);
211 var parts = phrase.split(" ");
212 var proper = [];
213 for (var i=0; i<parts.length; i++) {
214 var part = parts[i];
215 if (part.length > 0) {
216 // TODO check that lowercasing is always valid to do
217 proper.push(part.toLowerCase());
218 }
219 }
220 // TODO some levenstein on the words
221 var properPhrase = proper.join(' ');
222 // Check the words are valid
223 var isValid = mnemonic.check(properPhrase);
224 if (!isValid) {
225 return "Invalid mnemonic";
226 }
227 return false;
228 }
229
230 function findDerivationPathErrors(path) {
231 // TODO
232 return false;
233 }
234
235 function displayBip32Info() {
236 // Display the key
237 DOM.seed.val(seed);
238 var rootKey = bip32RootKey.toBase58();
239 DOM.rootKey.val(rootKey);
240 var extendedPrivKey = bip32ExtendedKey.toBase58();
241 DOM.extendedPrivKey.val(extendedPrivKey);
242 var extendedPubKey = bip32ExtendedKey.toBase58(false);
243 DOM.extendedPubKey.val(extendedPubKey);
244 // Display the addresses and privkeys
245 clearAddressesList();
246 displayAddresses(0, 20);
247 }
248
249 function displayAddresses(start, total) {
250 for (var i=0; i<total; i++) {
251 var index = i + start;
252 new TableRow(index);
253 }
254 }
255
256 function TableRow(index) {
257
258 function init() {
259 calculateValues();
260 }
261
262 function calculateValues() {
263 setTimeout(function() {
264 var key = bip32ExtendedKey.derive(index);
265 var address = key.getAddress().toString();
266 var privkey = key.privKey.toWIF(network);
267 addAddressToList(index, address, privkey);
268 }, 50)
269 }
270
271 init();
272
273 }
274
275 function showMore() {
276 var start = DOM.addresses.children().length;
277 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
278 if (isNaN(rowsToAdd)) {
279 rowsToAdd = 20;
280 DOM.rowsToAdd.val("20");
281 }
282 if (rowsToAdd > 200) {
283 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
284 msg += "Do you want to continue?";
285 if (!confirm(msg)) {
286 return;
287 }
288 }
289 displayAddresses(start, rowsToAdd);
290 }
291
292 function clearDisplay() {
293 clearAddressesList();
294 clearKey();
295 hideValidationError();
296 }
297
298 function clearAddressesList() {
299 DOM.addresses.empty();
300 }
301
302 function clearKey() {
303 DOM.rootKey.val("");
304 DOM.extendedPrivKey.val("");
305 DOM.extendedPubKey.val("");
306 }
307
308 function addAddressToList(index, address, privkey) {
309 var row = $(addressRowTemplate.html());
310 // Elements
311 var indexCell = row.find(".index span");
312 var addressCell = row.find(".address span");
313 var privkeyCell = row.find(".privkey span");
314 // Content
315 var indexText = derivationPath + "/" + index;
316 indexCell.text(indexText);
317 addressCell.text(address);
318 privkeyCell.text(privkey);
319 // Visibility
320 if (!showIndex) {
321 indexCell.addClass("invisible");
322 }
323 if (!showAddress) {
324 addressCell.addClass("invisible");
325 }
326 if (!showPrivKey) {
327 privkeyCell.addClass("invisible");
328 }
329 DOM.addresses.append(row);
330 }
331
332 function hasStrongRandom() {
333 return 'crypto' in window && window['crypto'] !== null;
334 }
335
336 function disableForms() {
337 $("form").on("submit", function(e) {
338 e.preventDefault();
339 });
340 }
341
342 function setBip44DerivationPath() {
343 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
344 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
345 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
346 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
347 var path = "m/";
348 path += purpose + "'/";
349 path += coin + "'/";
350 path += account + "'/";
351 path += change;
352 DOM.bip44path.val(path);
353 derivationPath = DOM.bip44path.val();
354 }
355
356 function parseIntNoNaN(val, defaultVal) {
357 var v = parseInt(val);
358 if (isNaN(v)) {
359 return defaultVal;
360 }
361 return v;
362 }
363
364 function showPending() {
365 DOM.feedback
366 .text("Calculating...")
367 .show();
368 }
369
370 function hidePending() {
371 DOM.feedback
372 .text("")
373 .hide();
374 }
375
376 function populateNetworkSelect() {
377 for (var i=0; i<networks.length; i++) {
378 var network = networks[i];
379 var option = $("<option>");
380 option.attr("value", i);
381 option.text(network.name);
382 DOM.phraseNetwork.append(option);
383 }
384 }
385
386 var networks = [
387 {
388 name: "Bitcoin",
389 onSelect: function() {
390 network = bitcoin.networks.bitcoin;
391 DOM.bip44coin.val(0);
392 },
393 },
394 {
395 name: "Bitcoin Testnet",
396 onSelect: function() {
397 network = bitcoin.networks.testnet;
398 DOM.bip44coin.val(1);
399 },
400 },
401 {
402 name: "Litecoin",
403 onSelect: function() {
404 network = bitcoin.networks.litecoin;
405 DOM.bip44coin.val(2);
406 },
407 },
408 {
409 name: "Dogecoin",
410 onSelect: function() {
411 network = bitcoin.networks.dogecoin;
412 DOM.bip44coin.val(3);
413 },
414 },
415 {
416 name: "ShadowCash",
417 onSelect: function() {
418 network = bitcoin.networks.shadow;
419 DOM.bip44coin.val(35);
420 },
421 },
422 {
423 name: "ShadowCash Testnet",
424 onSelect: function() {
425 network = bitcoin.networks.shadowtn;
426 DOM.bip44coin.val(1);
427 },
428 },
429 {
430 name: "Viacoin",
431 onSelect: function() {
432 network = bitcoin.networks.viacoin;
433 DOM.bip44coin.val(14);
434 },
435 },
436 {
437 name: "Viacoin Testnet",
438 onSelect: function() {
439 network = bitcoin.networks.viacointestnet;
440 DOM.bip44coin.val(1);
441 },
442 },
443 {
444 name: "Jumbucks",
445 onSelect: function() {
446 network = bitcoin.networks.jumbucks;
447 DOM.bip44coin.val(26);
448 },
449 },
450 {
451 name: "CLAM",
452 onSelect: function() {
453 network = bitcoin.networks.clam;
454 DOM.bip44coin.val(23);
455 },
456 },
457 ]
458
459 init();
460
461 })();