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