]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
Derivation path set correctly when changing coins
[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;
1759e5e8 6 var network = bitcoin.networks.bitcoin;
ebd8d4e8
IC
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");
43
42887888 44 var derivationPath = $(".tab-pane.active .path").val();
ebd8d4e8
IC
45
46 function init() {
47 // Events
d6cedc94 48 DOM.network.on("change", networkChanged);
a19a5498
IC
49 DOM.phrase.on("input", delayedPhraseChanged);
50 DOM.passphrase.on("input", delayedPhraseChanged);
ebd8d4e8
IC
51 DOM.generate.on("click", generateClicked);
52 DOM.more.on("click", showMore);
a19a5498
IC
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);
ebd8d4e8
IC
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();
7f15cb6e 65 populateNetworkSelect();
ebd8d4e8
IC
66 }
67
68 // Event handlers
69
d6cedc94 70 function networkChanged(e) {
7a995731 71 var network = e.target.value;
7f15cb6e 72 networks[network].onSelect();
1543fdbf 73 setBip44DerivationPath();
d6cedc94
IC
74 delayedPhraseChanged();
75 }
76
ebd8d4e8
IC
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();
1abcc511 91 var passphrase = DOM.passphrase.val();
ebd8d4e8
IC
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
1abcc511 104 calcBip32Seed(phrase, passphrase, derivationPath);
ebd8d4e8
IC
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() {
59780293 128 delayedPhraseChanged();
ebd8d4e8
IC
129 }
130
131 function bip32Changed() {
132 derivationPath = DOM.bip32path.val();
133 derivationChanged();
134 }
135
136 function bip44Changed() {
137 setBip44DerivationPath();
ebd8d4e8
IC
138 derivationChanged();
139 }
140
141 function toggleIndexes() {
700901cd 142 showIndex = !showIndex;
ebd8d4e8
IC
143 $("td.index span").toggleClass("invisible");
144 }
145
146 function toggleAddresses() {
700901cd 147 showAddress = !showAddress;
ebd8d4e8
IC
148 $("td.address span").toggleClass("invisible");
149 }
150
151 function togglePrivateKeys() {
700901cd 152 showPrivKey = !showPrivKey;
ebd8d4e8
IC
153 $("td.privkey span").toggleClass("invisible");
154 }
155
156 // Private methods
157
158 function generateRandomPhrase() {
159 if (!hasStrongRandom()) {
160 var errorText = "This browser does not support strong randomness";
161 showValidationError(errorText);
162 return;
163 }
164 var numWords = parseInt(DOM.strength.val());
165 // Check strength is an integer
166 if (isNaN(numWords)) {
167 DOM.strength.val("12");
168 numWords = 12;
169 }
170 // Check strength is a multiple of 32, if not round it down
171 if (numWords % 3 != 0) {
172 numWords = Math.floor(numWords / 3) * 3;
173 DOM.strength.val(numWords);
174 }
175 // Check strength is at least 32
176 if (numWords == 0) {
177 numWords = 3;
178 DOM.strength.val(numWords);
179 }
180 var strength = numWords / 3 * 32;
181 var words = mnemonic.generate(strength);
182 DOM.phrase.val(words);
183 return words;
184 }
185
1abcc511
PR
186 function calcBip32Seed(phrase, passphrase, path) {
187 var seed = mnemonic.toSeed(phrase, passphrase);
1759e5e8 188 bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network);
ebd8d4e8
IC
189 bip32ExtendedKey = bip32RootKey;
190 // Derive the key from the path
191 var pathBits = path.split("/");
192 for (var i=0; i<pathBits.length; i++) {
193 var bit = pathBits[i];
194 var index = parseInt(bit);
195 if (isNaN(index)) {
196 continue;
197 }
198 var hardened = bit[bit.length-1] == "'";
199 if (hardened) {
200 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
201 }
202 else {
203 bip32ExtendedKey = bip32ExtendedKey.derive(index);
204 }
205 }
206 }
207
208 function showValidationError(errorText) {
209 DOM.feedback
210 .text(errorText)
211 .show();
212 }
213
214 function hideValidationError() {
215 DOM.feedback
216 .text("")
217 .hide();
218 }
219
220 function findPhraseErrors(phrase) {
221 // TODO make this right
222 // Preprocess the words
783981de 223 phrase = mnemonic.normalizeString(phrase);
ebd8d4e8
IC
224 var parts = phrase.split(" ");
225 var proper = [];
226 for (var i=0; i<parts.length; i++) {
227 var part = parts[i];
228 if (part.length > 0) {
229 // TODO check that lowercasing is always valid to do
230 proper.push(part.toLowerCase());
231 }
232 }
233 // TODO some levenstein on the words
234 var properPhrase = proper.join(' ');
235 // Check the words are valid
236 var isValid = mnemonic.check(properPhrase);
237 if (!isValid) {
238 return "Invalid mnemonic";
239 }
240 return false;
241 }
242
243 function findDerivationPathErrors(path) {
244 // TODO
245 return false;
246 }
247
248 function displayBip32Info() {
249 // Display the key
250 var rootKey = bip32RootKey.toBase58();
251 DOM.rootKey.val(rootKey);
252 var extendedPrivKey = bip32ExtendedKey.toBase58();
253 DOM.extendedPrivKey.val(extendedPrivKey);
254 var extendedPubKey = bip32ExtendedKey.toBase58(false);
255 DOM.extendedPubKey.val(extendedPubKey);
256 // Display the addresses and privkeys
257 clearAddressesList();
258 displayAddresses(0, 20);
259 }
260
261 function displayAddresses(start, total) {
262 for (var i=0; i<total; i++) {
a8c45487
IC
263 var index = i + start;
264 new TableRow(index);
ebd8d4e8
IC
265 }
266 }
267
a8c45487
IC
268 function TableRow(index) {
269
270 function init() {
271 calculateValues();
272 }
273
274 function calculateValues() {
275 setTimeout(function() {
276 var key = bip32ExtendedKey.derive(index);
277 var address = key.getAddress().toString();
278 var privkey = key.privKey.toWIF(network);
279 addAddressToList(index, address, privkey);
280 }, 50)
281 }
282
283 init();
284
285 }
286
ebd8d4e8
IC
287 function showMore() {
288 var start = DOM.addresses.children().length;
289 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
290 if (isNaN(rowsToAdd)) {
291 rowsToAdd = 20;
292 DOM.rowsToAdd.val("20");
293 }
294 if (rowsToAdd > 200) {
295 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
296 msg += "Do you want to continue?";
297 if (!confirm(msg)) {
298 return;
299 }
300 }
ebd8d4e8 301 displayAddresses(start, rowsToAdd);
ebd8d4e8
IC
302 }
303
304 function clearDisplay() {
305 clearAddressesList();
306 clearKey();
307 hideValidationError();
308 }
309
310 function clearAddressesList() {
311 DOM.addresses.empty();
312 }
313
314 function clearKey() {
315 DOM.rootKey.val("");
316 DOM.extendedPrivKey.val("");
317 DOM.extendedPubKey.val("");
318 }
319
320 function addAddressToList(index, address, privkey) {
321 var row = $(addressRowTemplate.html());
700901cd
IC
322 // Elements
323 var indexCell = row.find(".index span");
324 var addressCell = row.find(".address span");
325 var privkeyCell = row.find(".privkey span");
326 // Content
ae30fed8
IC
327 var indexText = derivationPath + "/" + index;
328 indexCell.text(indexText);
700901cd
IC
329 addressCell.text(address);
330 privkeyCell.text(privkey);
331 // Visibility
332 if (!showIndex) {
333 indexCell.addClass("invisible");
334 }
335 if (!showAddress) {
336 addressCell.addClass("invisible");
337 }
338 if (!showPrivKey) {
339 privkeCell.addClass("invisible");
340 }
ebd8d4e8
IC
341 DOM.addresses.append(row);
342 }
343
344 function hasStrongRandom() {
345 return 'crypto' in window && window['crypto'] !== null;
346 }
347
348 function disableForms() {
349 $("form").on("submit", function(e) {
350 e.preventDefault();
351 });
352 }
353
354 function setBip44DerivationPath() {
355 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
356 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
357 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
358 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
359 var path = "m/";
360 path += purpose + "'/";
361 path += coin + "'/";
362 path += account + "'/";
363 path += change;
364 DOM.bip44path.val(path);
2b831bc6 365 derivationPath = DOM.bip44path.val();
ebd8d4e8
IC
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
7f15cb6e
IC
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 {
7a995731
IC
400 name: "Bitcoin",
401 onSelect: function() {
1759e5e8 402 network = bitcoin.networks.bitcoin;
7a995731 403 DOM.bip44coin.val(0);
7a995731
IC
404 },
405 },
7f15cb6e 406 {
7a995731
IC
407 name: "Bitcoin Testnet",
408 onSelect: function() {
1759e5e8 409 network = bitcoin.networks.testnet;
7a995731 410 DOM.bip44coin.val(1);
7a995731
IC
411 },
412 },
7f15cb6e 413 {
7a995731
IC
414 name: "Litecoin",
415 onSelect: function() {
1759e5e8 416 network = bitcoin.networks.litecoin;
7a995731
IC
417 DOM.bip44coin.val(2);
418 },
419 },
7f15cb6e 420 {
7a995731
IC
421 name: "Dogecoin",
422 onSelect: function() {
1759e5e8 423 network = bitcoin.networks.dogecoin;
7a995731
IC
424 DOM.bip44coin.val(3);
425 },
426 },
e3a9508c
IC
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 },
a3baa26e
IC
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 },
7f15cb6e 462 ]
7a995731 463
ebd8d4e8
IC
464 init();
465
466})();