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