]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
Add dash
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / index.js
CommitLineData
ebd8d4e8
IC
1(function() {
2
3 var mnemonic = new Mnemonic("english");
3e0ed16a 4 var seed = null
ebd8d4e8
IC
5 var bip32RootKey = null;
6 var bip32ExtendedKey = null;
1759e5e8 7 var network = bitcoin.networks.bitcoin;
ebd8d4e8
IC
8 var addressRowTemplate = $("#address-row-template");
9
700901cd
IC
10 var showIndex = true;
11 var showAddress = true;
12 var showPrivKey = true;
13
ebd8d4e8 14 var phraseChangeTimeoutEvent = null;
efe41586 15 var rootKeyChangedTimeoutEvent = null;
ebd8d4e8
IC
16
17 var DOM = {};
d6cedc94
IC
18 DOM.network = $(".network");
19 DOM.phraseNetwork = $("#network-phrase");
ebd8d4e8 20 DOM.phrase = $(".phrase");
1abcc511 21 DOM.passphrase = $(".passphrase");
ebd8d4e8 22 DOM.generate = $(".generate");
3e0ed16a 23 DOM.seed = $(".seed");
ebd8d4e8
IC
24 DOM.rootKey = $(".root-key");
25 DOM.extendedPrivKey = $(".extended-priv-key");
26 DOM.extendedPubKey = $(".extended-pub-key");
d6cedc94
IC
27 DOM.bip32tab = $("#bip32-tab");
28 DOM.bip44tab = $("#bip44-tab");
29 DOM.bip32panel = $("#bip32");
30 DOM.bip44panel = $("#bip44");
ebd8d4e8
IC
31 DOM.bip32path = $("#bip32-path");
32 DOM.bip44path = $("#bip44-path");
33 DOM.bip44purpose = $("#bip44 .purpose");
34 DOM.bip44coin = $("#bip44 .coin");
35 DOM.bip44account = $("#bip44 .account");
36 DOM.bip44change = $("#bip44 .change");
37 DOM.strength = $(".strength");
146e089e 38 DOM.hardenedAddresses = $(".hardened-addresses");
ebd8d4e8
IC
39 DOM.addresses = $(".addresses");
40 DOM.rowsToAdd = $(".rows-to-add");
41 DOM.more = $(".more");
42 DOM.feedback = $(".feedback");
43 DOM.tab = $(".derivation-type a");
44 DOM.indexToggle = $(".index-toggle");
45 DOM.addressToggle = $(".address-toggle");
46 DOM.privateKeyToggle = $(".private-key-toggle");
47
ebd8d4e8
IC
48 function init() {
49 // Events
d6cedc94 50 DOM.network.on("change", networkChanged);
a19a5498
IC
51 DOM.phrase.on("input", delayedPhraseChanged);
52 DOM.passphrase.on("input", delayedPhraseChanged);
ebd8d4e8
IC
53 DOM.generate.on("click", generateClicked);
54 DOM.more.on("click", showMore);
efe41586
IC
55 DOM.rootKey.on("input", delayedRootKeyChanged);
56 DOM.bip32path.on("input", calcForDerivationPath);
57 DOM.bip44purpose.on("input", calcForDerivationPath);
58 DOM.bip44coin.on("input", calcForDerivationPath);
59 DOM.bip44account.on("input", calcForDerivationPath);
60 DOM.bip44change.on("input", calcForDerivationPath);
61 DOM.tab.on("shown.bs.tab", calcForDerivationPath);
146e089e 62 DOM.hardenedAddresses.on("change", calcForDerivationPath);
ebd8d4e8
IC
63 DOM.indexToggle.on("click", toggleIndexes);
64 DOM.addressToggle.on("click", toggleAddresses);
65 DOM.privateKeyToggle.on("click", togglePrivateKeys);
66 disableForms();
67 hidePending();
68 hideValidationError();
7f15cb6e 69 populateNetworkSelect();
ebd8d4e8
IC
70 }
71
72 // Event handlers
73
d6cedc94 74 function networkChanged(e) {
54563907
IC
75 var networkIndex = e.target.value;
76 networks[networkIndex].onSelect();
77 if (seed != null) {
78 phraseChanged();
79 }
80 else {
81 rootKeyChanged();
82 }
d6cedc94
IC
83 }
84
ebd8d4e8
IC
85 function delayedPhraseChanged() {
86 hideValidationError();
87 showPending();
88 if (phraseChangeTimeoutEvent != null) {
89 clearTimeout(phraseChangeTimeoutEvent);
90 }
91 phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
92 }
93
94 function phraseChanged() {
95 showPending();
96 hideValidationError();
97 // Get the mnemonic phrase
98 var phrase = DOM.phrase.val();
99 var errorText = findPhraseErrors(phrase);
100 if (errorText) {
101 showValidationError(errorText);
102 return;
103 }
efe41586
IC
104 // Calculate and display
105 var passphrase = DOM.passphrase.val();
106 calcBip32RootKeyFromSeed(phrase, passphrase);
107 calcForDerivationPath();
108 hidePending();
109 }
110
111 function delayedRootKeyChanged() {
112 // Warn if there is an existing mnemonic or passphrase.
113 if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) {
114 if (!confirm("This will clear existing mnemonic and passphrase")) {
115 DOM.rootKey.val(bip32RootKey);
116 return
117 }
118 }
119 hideValidationError();
120 showPending();
121 // Clear existing mnemonic and passphrase
122 DOM.phrase.val("");
123 DOM.passphrase.val("");
124 seed = null;
125 if (rootKeyChangedTimeoutEvent != null) {
126 clearTimeout(rootKeyChangedTimeoutEvent);
127 }
128 rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400);
129 }
130
131 function rootKeyChanged() {
132 showPending();
133 hideValidationError();
134 // Validate the root key TODO
135 var rootKeyBase58 = DOM.rootKey.val();
136 var errorText = validateRootKey(rootKeyBase58);
137 if (errorText) {
138 showValidationError(errorText);
139 return;
140 }
141 // Calculate and display
142 calcBip32RootKeyFromBase58(rootKeyBase58);
143 calcForDerivationPath();
144 hidePending();
145 }
146
147 function calcForDerivationPath() {
148 showPending();
149 hideValidationError();
ebd8d4e8 150 // Get the derivation path
38523d36
IC
151 var derivationPath = getDerivationPath();
152 var errorText = findDerivationPathErrors(derivationPath);
ebd8d4e8
IC
153 if (errorText) {
154 showValidationError(errorText);
155 return;
156 }
efe41586 157 calcBip32ExtendedKey(derivationPath);
ebd8d4e8
IC
158 displayBip32Info();
159 hidePending();
160 }
161
162 function generateClicked() {
163 clearDisplay();
164 showPending();
165 setTimeout(function() {
166 var phrase = generateRandomPhrase();
167 if (!phrase) {
168 return;
169 }
170 phraseChanged();
171 }, 50);
172 }
173
ebd8d4e8 174 function toggleIndexes() {
700901cd 175 showIndex = !showIndex;
ebd8d4e8
IC
176 $("td.index span").toggleClass("invisible");
177 }
178
179 function toggleAddresses() {
700901cd 180 showAddress = !showAddress;
ebd8d4e8
IC
181 $("td.address span").toggleClass("invisible");
182 }
183
184 function togglePrivateKeys() {
700901cd 185 showPrivKey = !showPrivKey;
ebd8d4e8
IC
186 $("td.privkey span").toggleClass("invisible");
187 }
188
189 // Private methods
190
191 function generateRandomPhrase() {
192 if (!hasStrongRandom()) {
193 var errorText = "This browser does not support strong randomness";
194 showValidationError(errorText);
195 return;
196 }
197 var numWords = parseInt(DOM.strength.val());
ebd8d4e8
IC
198 var strength = numWords / 3 * 32;
199 var words = mnemonic.generate(strength);
200 DOM.phrase.val(words);
201 return words;
202 }
203
efe41586 204 function calcBip32RootKeyFromSeed(phrase, passphrase) {
3e0ed16a 205 seed = mnemonic.toSeed(phrase, passphrase);
1759e5e8 206 bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network);
efe41586
IC
207 }
208
209 function calcBip32RootKeyFromBase58(rootKeyBase58) {
210 bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58);
211 }
212
213 function calcBip32ExtendedKey(path) {
ebd8d4e8
IC
214 bip32ExtendedKey = bip32RootKey;
215 // Derive the key from the path
216 var pathBits = path.split("/");
217 for (var i=0; i<pathBits.length; i++) {
218 var bit = pathBits[i];
219 var index = parseInt(bit);
220 if (isNaN(index)) {
221 continue;
222 }
223 var hardened = bit[bit.length-1] == "'";
224 if (hardened) {
225 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
226 }
227 else {
228 bip32ExtendedKey = bip32ExtendedKey.derive(index);
229 }
230 }
231 }
232
233 function showValidationError(errorText) {
234 DOM.feedback
235 .text(errorText)
236 .show();
237 }
238
239 function hideValidationError() {
240 DOM.feedback
241 .text("")
242 .hide();
243 }
244
245 function findPhraseErrors(phrase) {
246 // TODO make this right
247 // Preprocess the words
783981de 248 phrase = mnemonic.normalizeString(phrase);
ebd8d4e8
IC
249 var parts = phrase.split(" ");
250 var proper = [];
251 for (var i=0; i<parts.length; i++) {
252 var part = parts[i];
253 if (part.length > 0) {
254 // TODO check that lowercasing is always valid to do
255 proper.push(part.toLowerCase());
256 }
257 }
ebd8d4e8 258 var properPhrase = proper.join(' ');
563e401a
IC
259 // Check each word
260 for (var i=0; i<proper.length; i++) {
261 var word = proper[i];
262 if (WORDLISTS["english"].indexOf(word) == -1) {
263 console.log("Finding closest match to " + word);
264 var nearestWord = findNearestWord(word);
265 return word + " not in wordlist, did you mean " + nearestWord + "?";
266 }
267 }
ebd8d4e8
IC
268 // Check the words are valid
269 var isValid = mnemonic.check(properPhrase);
270 if (!isValid) {
271 return "Invalid mnemonic";
272 }
273 return false;
274 }
275
efe41586
IC
276 function validateRootKey(rootKeyBase58) {
277 try {
278 bitcoin.HDNode.fromBase58(rootKeyBase58);
279 }
280 catch (e) {
281 return "Invalid root key";
282 }
283 return "";
284 }
285
38523d36
IC
286 function getDerivationPath() {
287 if (DOM.bip44tab.hasClass("active")) {
288 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
289 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
290 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
291 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
292 var path = "m/";
293 path += purpose + "'/";
294 path += coin + "'/";
295 path += account + "'/";
296 path += change;
297 DOM.bip44path.val(path);
298 var derivationPath = DOM.bip44path.val();
299 console.log("Using derivation path from BIP44 tab: " + derivationPath);
300 return derivationPath;
301 }
302 else if (DOM.bip32tab.hasClass("active")) {
303 var derivationPath = DOM.bip32path.val();
304 console.log("Using derivation path from BIP32 tab: " + derivationPath);
305 return derivationPath;
306 }
307 else {
308 console.log("Unknown derivation path");
309 }
310 }
311
ebd8d4e8 312 function findDerivationPathErrors(path) {
30c9e79d
IC
313 // TODO is not perfect but is better than nothing
314 // Inspired by
315 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
316 // and
317 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
318 var maxDepth = 255; // TODO verify this!!
319 var maxIndexValue = Math.pow(2, 31); // TODO verify this!!
320 if (path[0] != "m") {
321 return "First character must be 'm'";
322 }
323 if (path.length > 1) {
324 if (path[1] != "/") {
325 return "Separator must be '/'";
326 }
327 var indexes = path.split("/");
328 if (indexes.length > maxDepth) {
329 return "Derivation depth is " + indexes.length + ", must be less than " + maxDepth;
330 }
331 for (var depth = 1; depth<indexes.length; depth++) {
332 var index = indexes[depth];
333 var invalidChars = index.replace(/^[0-9]+'?$/g, "")
334 if (invalidChars.length > 0) {
335 return "Invalid characters " + invalidChars + " found at depth " + depth;
336 }
337 var indexValue = parseInt(index.replace("'", ""));
338 if (isNaN(depth)) {
339 return "Invalid number at depth " + depth;
340 }
341 if (indexValue > maxIndexValue) {
342 return "Value of " + indexValue + " at depth " + depth + " must be less than " + maxIndexValue;
343 }
344 }
345 }
ebd8d4e8
IC
346 return false;
347 }
348
349 function displayBip32Info() {
350 // Display the key
3e0ed16a 351 DOM.seed.val(seed);
ebd8d4e8
IC
352 var rootKey = bip32RootKey.toBase58();
353 DOM.rootKey.val(rootKey);
354 var extendedPrivKey = bip32ExtendedKey.toBase58();
355 DOM.extendedPrivKey.val(extendedPrivKey);
356 var extendedPubKey = bip32ExtendedKey.toBase58(false);
357 DOM.extendedPubKey.val(extendedPubKey);
358 // Display the addresses and privkeys
359 clearAddressesList();
360 displayAddresses(0, 20);
361 }
362
363 function displayAddresses(start, total) {
364 for (var i=0; i<total; i++) {
a8c45487
IC
365 var index = i + start;
366 new TableRow(index);
ebd8d4e8
IC
367 }
368 }
369
a8c45487
IC
370 function TableRow(index) {
371
146e089e
IC
372 var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
373
a8c45487
IC
374 function init() {
375 calculateValues();
376 }
377
378 function calculateValues() {
379 setTimeout(function() {
146e089e
IC
380 var key = "";
381 if (useHardenedAddresses) {
382 key = bip32ExtendedKey.deriveHardened(index);
383 }
384 else {
385 key = bip32ExtendedKey.derive(index);
386 }
a8c45487
IC
387 var address = key.getAddress().toString();
388 var privkey = key.privKey.toWIF(network);
38523d36 389 var indexText = getDerivationPath() + "/" + index;
146e089e
IC
390 if (useHardenedAddresses) {
391 indexText = indexText + "'";
392 }
38523d36 393 addAddressToList(indexText, address, privkey);
a8c45487
IC
394 }, 50)
395 }
396
397 init();
398
399 }
400
ebd8d4e8
IC
401 function showMore() {
402 var start = DOM.addresses.children().length;
403 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
404 if (isNaN(rowsToAdd)) {
405 rowsToAdd = 20;
406 DOM.rowsToAdd.val("20");
407 }
408 if (rowsToAdd > 200) {
409 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
410 msg += "Do you want to continue?";
411 if (!confirm(msg)) {
412 return;
413 }
414 }
ebd8d4e8 415 displayAddresses(start, rowsToAdd);
ebd8d4e8
IC
416 }
417
418 function clearDisplay() {
419 clearAddressesList();
420 clearKey();
421 hideValidationError();
422 }
423
424 function clearAddressesList() {
425 DOM.addresses.empty();
426 }
427
428 function clearKey() {
429 DOM.rootKey.val("");
430 DOM.extendedPrivKey.val("");
431 DOM.extendedPubKey.val("");
432 }
433
38523d36 434 function addAddressToList(indexText, address, privkey) {
ebd8d4e8 435 var row = $(addressRowTemplate.html());
700901cd
IC
436 // Elements
437 var indexCell = row.find(".index span");
438 var addressCell = row.find(".address span");
439 var privkeyCell = row.find(".privkey span");
440 // Content
ae30fed8 441 indexCell.text(indexText);
700901cd
IC
442 addressCell.text(address);
443 privkeyCell.text(privkey);
444 // Visibility
445 if (!showIndex) {
446 indexCell.addClass("invisible");
447 }
448 if (!showAddress) {
449 addressCell.addClass("invisible");
450 }
451 if (!showPrivKey) {
6d628db7 452 privkeyCell.addClass("invisible");
700901cd 453 }
ebd8d4e8
IC
454 DOM.addresses.append(row);
455 }
456
457 function hasStrongRandom() {
458 return 'crypto' in window && window['crypto'] !== null;
459 }
460
461 function disableForms() {
462 $("form").on("submit", function(e) {
463 e.preventDefault();
464 });
465 }
466
ebd8d4e8
IC
467 function parseIntNoNaN(val, defaultVal) {
468 var v = parseInt(val);
469 if (isNaN(v)) {
470 return defaultVal;
471 }
472 return v;
473 }
474
475 function showPending() {
476 DOM.feedback
477 .text("Calculating...")
478 .show();
479 }
480
563e401a
IC
481 function findNearestWord(word) {
482 var words = WORDLISTS["english"];
483 var minDistance = 99;
484 var closestWord = words[0];
485 for (var i=0; i<words.length; i++) {
486 var comparedTo = words[i];
487 var distance = Levenshtein.get(word, comparedTo);
488 if (distance < minDistance) {
489 closestWord = comparedTo;
490 minDistance = distance;
491 }
492 }
493 return closestWord;
494 }
495
ebd8d4e8
IC
496 function hidePending() {
497 DOM.feedback
498 .text("")
499 .hide();
500 }
501
7f15cb6e
IC
502 function populateNetworkSelect() {
503 for (var i=0; i<networks.length; i++) {
504 var network = networks[i];
505 var option = $("<option>");
506 option.attr("value", i);
507 option.text(network.name);
508 DOM.phraseNetwork.append(option);
509 }
510 }
511
512 var networks = [
513 {
7a995731
IC
514 name: "Bitcoin",
515 onSelect: function() {
1759e5e8 516 network = bitcoin.networks.bitcoin;
7a995731 517 DOM.bip44coin.val(0);
7a995731
IC
518 },
519 },
7f15cb6e 520 {
7a995731
IC
521 name: "Bitcoin Testnet",
522 onSelect: function() {
1759e5e8 523 network = bitcoin.networks.testnet;
7a995731 524 DOM.bip44coin.val(1);
7a995731
IC
525 },
526 },
7f15cb6e 527 {
7a995731
IC
528 name: "Litecoin",
529 onSelect: function() {
1759e5e8 530 network = bitcoin.networks.litecoin;
7a995731
IC
531 DOM.bip44coin.val(2);
532 },
533 },
7f15cb6e 534 {
7a995731
IC
535 name: "Dogecoin",
536 onSelect: function() {
1759e5e8 537 network = bitcoin.networks.dogecoin;
7a995731
IC
538 DOM.bip44coin.val(3);
539 },
540 },
e3a9508c
IC
541 {
542 name: "ShadowCash",
543 onSelect: function() {
544 network = bitcoin.networks.shadow;
545 DOM.bip44coin.val(35);
546 },
547 },
548 {
549 name: "ShadowCash Testnet",
550 onSelect: function() {
551 network = bitcoin.networks.shadowtn;
552 DOM.bip44coin.val(1);
553 },
554 },
a3baa26e
IC
555 {
556 name: "Viacoin",
557 onSelect: function() {
558 network = bitcoin.networks.viacoin;
559 DOM.bip44coin.val(14);
560 },
561 },
562 {
563 name: "Viacoin Testnet",
564 onSelect: function() {
565 network = bitcoin.networks.viacointestnet;
566 DOM.bip44coin.val(1);
567 },
568 },
569 {
570 name: "Jumbucks",
571 onSelect: function() {
572 network = bitcoin.networks.jumbucks;
573 DOM.bip44coin.val(26);
574 },
575 },
5c434a8a
CM
576 {
577 name: "CLAM",
578 onSelect: function() {
579 network = bitcoin.networks.clam;
580 DOM.bip44coin.val(23);
581 },
582 },
7f15cb6e 583 ]
7a995731 584
ebd8d4e8
IC
585 init();
586
587})();