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