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