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