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