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