]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
Standalone contains ShadowCash
[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();
138 derivationPath = DOM.bip44path.val();
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());
166 // Check strength is an integer
167 if (isNaN(numWords)) {
168 DOM.strength.val("12");
169 numWords = 12;
170 }
171 // Check strength is a multiple of 32, if not round it down
172 if (numWords % 3 != 0) {
173 numWords = Math.floor(numWords / 3) * 3;
174 DOM.strength.val(numWords);
175 }
176 // Check strength is at least 32
177 if (numWords == 0) {
178 numWords = 3;
179 DOM.strength.val(numWords);
180 }
181 var strength = numWords / 3 * 32;
182 var words = mnemonic.generate(strength);
183 DOM.phrase.val(words);
184 return words;
185 }
186
1abcc511
PR
187 function calcBip32Seed(phrase, passphrase, path) {
188 var seed = mnemonic.toSeed(phrase, passphrase);
1759e5e8 189 bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network);
ebd8d4e8
IC
190 bip32ExtendedKey = bip32RootKey;
191 // Derive the key from the path
192 var pathBits = path.split("/");
193 for (var i=0; i<pathBits.length; i++) {
194 var bit = pathBits[i];
195 var index = parseInt(bit);
196 if (isNaN(index)) {
197 continue;
198 }
199 var hardened = bit[bit.length-1] == "'";
200 if (hardened) {
201 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
202 }
203 else {
204 bip32ExtendedKey = bip32ExtendedKey.derive(index);
205 }
206 }
207 }
208
209 function showValidationError(errorText) {
210 DOM.feedback
211 .text(errorText)
212 .show();
213 }
214
215 function hideValidationError() {
216 DOM.feedback
217 .text("")
218 .hide();
219 }
220
221 function findPhraseErrors(phrase) {
222 // TODO make this right
223 // Preprocess the words
783981de 224 phrase = mnemonic.normalizeString(phrase);
ebd8d4e8
IC
225 var parts = phrase.split(" ");
226 var proper = [];
227 for (var i=0; i<parts.length; i++) {
228 var part = parts[i];
229 if (part.length > 0) {
230 // TODO check that lowercasing is always valid to do
231 proper.push(part.toLowerCase());
232 }
233 }
234 // TODO some levenstein on the words
235 var properPhrase = proper.join(' ');
236 // Check the words are valid
237 var isValid = mnemonic.check(properPhrase);
238 if (!isValid) {
239 return "Invalid mnemonic";
240 }
241 return false;
242 }
243
244 function findDerivationPathErrors(path) {
245 // TODO
246 return false;
247 }
248
249 function displayBip32Info() {
250 // Display the key
251 var rootKey = bip32RootKey.toBase58();
252 DOM.rootKey.val(rootKey);
253 var extendedPrivKey = bip32ExtendedKey.toBase58();
254 DOM.extendedPrivKey.val(extendedPrivKey);
255 var extendedPubKey = bip32ExtendedKey.toBase58(false);
256 DOM.extendedPubKey.val(extendedPubKey);
257 // Display the addresses and privkeys
258 clearAddressesList();
259 displayAddresses(0, 20);
260 }
261
262 function displayAddresses(start, total) {
263 for (var i=0; i<total; i++) {
a8c45487
IC
264 var index = i + start;
265 new TableRow(index);
ebd8d4e8
IC
266 }
267 }
268
a8c45487
IC
269 function TableRow(index) {
270
271 function init() {
272 calculateValues();
273 }
274
275 function calculateValues() {
276 setTimeout(function() {
277 var key = bip32ExtendedKey.derive(index);
278 var address = key.getAddress().toString();
279 var privkey = key.privKey.toWIF(network);
280 addAddressToList(index, address, privkey);
281 }, 50)
282 }
283
284 init();
285
286 }
287
ebd8d4e8
IC
288 function showMore() {
289 var start = DOM.addresses.children().length;
290 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
291 if (isNaN(rowsToAdd)) {
292 rowsToAdd = 20;
293 DOM.rowsToAdd.val("20");
294 }
295 if (rowsToAdd > 200) {
296 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
297 msg += "Do you want to continue?";
298 if (!confirm(msg)) {
299 return;
300 }
301 }
ebd8d4e8 302 displayAddresses(start, rowsToAdd);
ebd8d4e8
IC
303 }
304
305 function clearDisplay() {
306 clearAddressesList();
307 clearKey();
308 hideValidationError();
309 }
310
311 function clearAddressesList() {
312 DOM.addresses.empty();
313 }
314
315 function clearKey() {
316 DOM.rootKey.val("");
317 DOM.extendedPrivKey.val("");
318 DOM.extendedPubKey.val("");
319 }
320
321 function addAddressToList(index, address, privkey) {
322 var row = $(addressRowTemplate.html());
700901cd
IC
323 // Elements
324 var indexCell = row.find(".index span");
325 var addressCell = row.find(".address span");
326 var privkeyCell = row.find(".privkey span");
327 // Content
328 indexCell.text(index);
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);
365 }
366
367 function parseIntNoNaN(val, defaultVal) {
368 var v = parseInt(val);
369 if (isNaN(v)) {
370 return defaultVal;
371 }
372 return v;
373 }
374
375 function showPending() {
376 DOM.feedback
377 .text("Calculating...")
378 .show();
379 }
380
381 function hidePending() {
382 DOM.feedback
383 .text("")
384 .hide();
385 }
386
7f15cb6e
IC
387 function populateNetworkSelect() {
388 for (var i=0; i<networks.length; i++) {
389 var network = networks[i];
390 var option = $("<option>");
391 option.attr("value", i);
392 option.text(network.name);
393 DOM.phraseNetwork.append(option);
394 }
395 }
396
397 var networks = [
398 {
7a995731
IC
399 name: "Bitcoin",
400 onSelect: function() {
1759e5e8 401 network = bitcoin.networks.bitcoin;
7a995731 402 DOM.bip44coin.val(0);
7a995731
IC
403 },
404 },
7f15cb6e 405 {
7a995731
IC
406 name: "Bitcoin Testnet",
407 onSelect: function() {
1759e5e8 408 network = bitcoin.networks.testnet;
7a995731 409 DOM.bip44coin.val(1);
7a995731
IC
410 },
411 },
7f15cb6e 412 {
7a995731
IC
413 name: "Litecoin",
414 onSelect: function() {
1759e5e8 415 network = bitcoin.networks.litecoin;
7a995731
IC
416 DOM.bip44coin.val(2);
417 },
418 },
7f15cb6e 419 {
7a995731
IC
420 name: "Dogecoin",
421 onSelect: function() {
1759e5e8 422 network = bitcoin.networks.dogecoin;
7a995731
IC
423 DOM.bip44coin.val(3);
424 },
425 },
e3a9508c
IC
426 {
427 name: "ShadowCash",
428 onSelect: function() {
429 network = bitcoin.networks.shadow;
430 DOM.bip44coin.val(35);
431 },
432 },
433 {
434 name: "ShadowCash Testnet",
435 onSelect: function() {
436 network = bitcoin.networks.shadowtn;
437 DOM.bip44coin.val(1);
438 },
439 },
7f15cb6e 440 ]
7a995731 441
ebd8d4e8
IC
442 init();
443
444})();