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