]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame_incremental - src/js/index.js
Derivation Path global replaced with function
[perso/Immae/Projets/Cryptomonnaies/BIP39.git] / src / js / index.js
... / ...
CommitLineData
1(function() {
2
3 var mnemonic = new Mnemonic("english");
4 var seed = null
5 var bip32RootKey = null;
6 var bip32ExtendedKey = null;
7 var network = bitcoin.networks.bitcoin;
8 var addressRowTemplate = $("#address-row-template");
9
10 var showIndex = true;
11 var showAddress = true;
12 var showPrivKey = true;
13
14 var phraseChangeTimeoutEvent = null;
15
16 var DOM = {};
17 DOM.network = $(".network");
18 DOM.phraseNetwork = $("#network-phrase");
19 DOM.phrase = $(".phrase");
20 DOM.passphrase = $(".passphrase");
21 DOM.generate = $(".generate");
22 DOM.seed = $(".seed");
23 DOM.rootKey = $(".root-key");
24 DOM.extendedPrivKey = $(".extended-priv-key");
25 DOM.extendedPubKey = $(".extended-pub-key");
26 DOM.bip32tab = $("#bip32-tab");
27 DOM.bip44tab = $("#bip44-tab");
28 DOM.bip32panel = $("#bip32");
29 DOM.bip44panel = $("#bip44");
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 function init() {
47 // Events
48 DOM.network.on("change", networkChanged);
49 DOM.phrase.on("input", delayedPhraseChanged);
50 DOM.passphrase.on("input", delayedPhraseChanged);
51 DOM.generate.on("click", generateClicked);
52 DOM.more.on("click", showMore);
53 DOM.bip32path.on("input", delayedPhraseChanged);
54 DOM.bip44purpose.on("input", delayedPhraseChanged);
55 DOM.bip44coin.on("input", delayedPhraseChanged);
56 DOM.bip44account.on("input", delayedPhraseChanged);
57 DOM.bip44change.on("input", delayedPhraseChanged);
58 DOM.tab.on("click", delayedPhraseChanged);
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 populateNetworkSelect();
66 }
67
68 // Event handlers
69
70 function networkChanged(e) {
71 var network = e.target.value;
72 networks[network].onSelect();
73 delayedPhraseChanged();
74 }
75
76 function delayedPhraseChanged() {
77 hideValidationError();
78 showPending();
79 if (phraseChangeTimeoutEvent != null) {
80 clearTimeout(phraseChangeTimeoutEvent);
81 }
82 phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
83 }
84
85 function phraseChanged() {
86 showPending();
87 hideValidationError();
88 // Get the mnemonic phrase
89 var phrase = DOM.phrase.val();
90 var passphrase = DOM.passphrase.val();
91 var errorText = findPhraseErrors(phrase);
92 if (errorText) {
93 showValidationError(errorText);
94 return;
95 }
96 // Get the derivation path
97 var derivationPath = getDerivationPath();
98 var errorText = findDerivationPathErrors(derivationPath);
99 if (errorText) {
100 showValidationError(errorText);
101 return;
102 }
103 // Calculate and display
104 calcBip32Seed(phrase, passphrase, derivationPath);
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 toggleIndexes() {
122 showIndex = !showIndex;
123 $("td.index span").toggleClass("invisible");
124 }
125
126 function toggleAddresses() {
127 showAddress = !showAddress;
128 $("td.address span").toggleClass("invisible");
129 }
130
131 function togglePrivateKeys() {
132 showPrivKey = !showPrivKey;
133 $("td.privkey span").toggleClass("invisible");
134 }
135
136 // Private methods
137
138 function generateRandomPhrase() {
139 if (!hasStrongRandom()) {
140 var errorText = "This browser does not support strong randomness";
141 showValidationError(errorText);
142 return;
143 }
144 var numWords = parseInt(DOM.strength.val());
145 var strength = numWords / 3 * 32;
146 var words = mnemonic.generate(strength);
147 DOM.phrase.val(words);
148 return words;
149 }
150
151 function calcBip32Seed(phrase, passphrase, path) {
152 seed = mnemonic.toSeed(phrase, passphrase);
153 bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network);
154 bip32ExtendedKey = bip32RootKey;
155 // Derive the key from the path
156 var pathBits = path.split("/");
157 for (var i=0; i<pathBits.length; i++) {
158 var bit = pathBits[i];
159 var index = parseInt(bit);
160 if (isNaN(index)) {
161 continue;
162 }
163 var hardened = bit[bit.length-1] == "'";
164 if (hardened) {
165 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
166 }
167 else {
168 bip32ExtendedKey = bip32ExtendedKey.derive(index);
169 }
170 }
171 }
172
173 function showValidationError(errorText) {
174 DOM.feedback
175 .text(errorText)
176 .show();
177 }
178
179 function hideValidationError() {
180 DOM.feedback
181 .text("")
182 .hide();
183 }
184
185 function findPhraseErrors(phrase) {
186 // TODO make this right
187 // Preprocess the words
188 phrase = mnemonic.normalizeString(phrase);
189 var parts = phrase.split(" ");
190 var proper = [];
191 for (var i=0; i<parts.length; i++) {
192 var part = parts[i];
193 if (part.length > 0) {
194 // TODO check that lowercasing is always valid to do
195 proper.push(part.toLowerCase());
196 }
197 }
198 // TODO some levenstein on the words
199 var properPhrase = proper.join(' ');
200 // Check the words are valid
201 var isValid = mnemonic.check(properPhrase);
202 if (!isValid) {
203 return "Invalid mnemonic";
204 }
205 return false;
206 }
207
208 function getDerivationPath() {
209 if (DOM.bip44tab.hasClass("active")) {
210 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
211 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
212 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
213 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
214 var path = "m/";
215 path += purpose + "'/";
216 path += coin + "'/";
217 path += account + "'/";
218 path += change;
219 DOM.bip44path.val(path);
220 var derivationPath = DOM.bip44path.val();
221 console.log("Using derivation path from BIP44 tab: " + derivationPath);
222 return derivationPath;
223 }
224 else if (DOM.bip32tab.hasClass("active")) {
225 var derivationPath = DOM.bip32path.val();
226 console.log("Using derivation path from BIP32 tab: " + derivationPath);
227 return derivationPath;
228 }
229 else {
230 console.log("Unknown derivation path");
231 }
232 }
233
234 function findDerivationPathErrors(path) {
235 // TODO
236 return false;
237 }
238
239 function displayBip32Info() {
240 // Display the key
241 DOM.seed.val(seed);
242 var rootKey = bip32RootKey.toBase58();
243 DOM.rootKey.val(rootKey);
244 var extendedPrivKey = bip32ExtendedKey.toBase58();
245 DOM.extendedPrivKey.val(extendedPrivKey);
246 var extendedPubKey = bip32ExtendedKey.toBase58(false);
247 DOM.extendedPubKey.val(extendedPubKey);
248 // Display the addresses and privkeys
249 clearAddressesList();
250 displayAddresses(0, 20);
251 }
252
253 function displayAddresses(start, total) {
254 for (var i=0; i<total; i++) {
255 var index = i + start;
256 new TableRow(index);
257 }
258 }
259
260 function TableRow(index) {
261
262 function init() {
263 calculateValues();
264 }
265
266 function calculateValues() {
267 setTimeout(function() {
268 var key = bip32ExtendedKey.derive(index);
269 var address = key.getAddress().toString();
270 var privkey = key.privKey.toWIF(network);
271 var indexText = getDerivationPath() + "/" + index;
272 addAddressToList(indexText, address, privkey);
273 }, 50)
274 }
275
276 init();
277
278 }
279
280 function showMore() {
281 var start = DOM.addresses.children().length;
282 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
283 if (isNaN(rowsToAdd)) {
284 rowsToAdd = 20;
285 DOM.rowsToAdd.val("20");
286 }
287 if (rowsToAdd > 200) {
288 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
289 msg += "Do you want to continue?";
290 if (!confirm(msg)) {
291 return;
292 }
293 }
294 displayAddresses(start, rowsToAdd);
295 }
296
297 function clearDisplay() {
298 clearAddressesList();
299 clearKey();
300 hideValidationError();
301 }
302
303 function clearAddressesList() {
304 DOM.addresses.empty();
305 }
306
307 function clearKey() {
308 DOM.rootKey.val("");
309 DOM.extendedPrivKey.val("");
310 DOM.extendedPubKey.val("");
311 }
312
313 function addAddressToList(indexText, address, privkey) {
314 var row = $(addressRowTemplate.html());
315 // Elements
316 var indexCell = row.find(".index span");
317 var addressCell = row.find(".address span");
318 var privkeyCell = row.find(".privkey span");
319 // Content
320 indexCell.text(indexText);
321 addressCell.text(address);
322 privkeyCell.text(privkey);
323 // Visibility
324 if (!showIndex) {
325 indexCell.addClass("invisible");
326 }
327 if (!showAddress) {
328 addressCell.addClass("invisible");
329 }
330 if (!showPrivKey) {
331 privkeyCell.addClass("invisible");
332 }
333 DOM.addresses.append(row);
334 }
335
336 function hasStrongRandom() {
337 return 'crypto' in window && window['crypto'] !== null;
338 }
339
340 function disableForms() {
341 $("form").on("submit", function(e) {
342 e.preventDefault();
343 });
344 }
345
346 function parseIntNoNaN(val, defaultVal) {
347 var v = parseInt(val);
348 if (isNaN(v)) {
349 return defaultVal;
350 }
351 return v;
352 }
353
354 function showPending() {
355 DOM.feedback
356 .text("Calculating...")
357 .show();
358 }
359
360 function hidePending() {
361 DOM.feedback
362 .text("")
363 .hide();
364 }
365
366 function populateNetworkSelect() {
367 for (var i=0; i<networks.length; i++) {
368 var network = networks[i];
369 var option = $("<option>");
370 option.attr("value", i);
371 option.text(network.name);
372 DOM.phraseNetwork.append(option);
373 }
374 }
375
376 var networks = [
377 {
378 name: "Bitcoin",
379 onSelect: function() {
380 network = bitcoin.networks.bitcoin;
381 DOM.bip44coin.val(0);
382 },
383 },
384 {
385 name: "Bitcoin Testnet",
386 onSelect: function() {
387 network = bitcoin.networks.testnet;
388 DOM.bip44coin.val(1);
389 },
390 },
391 {
392 name: "Litecoin",
393 onSelect: function() {
394 network = bitcoin.networks.litecoin;
395 DOM.bip44coin.val(2);
396 },
397 },
398 {
399 name: "Dogecoin",
400 onSelect: function() {
401 network = bitcoin.networks.dogecoin;
402 DOM.bip44coin.val(3);
403 },
404 },
405 {
406 name: "ShadowCash",
407 onSelect: function() {
408 network = bitcoin.networks.shadow;
409 DOM.bip44coin.val(35);
410 },
411 },
412 {
413 name: "ShadowCash Testnet",
414 onSelect: function() {
415 network = bitcoin.networks.shadowtn;
416 DOM.bip44coin.val(1);
417 },
418 },
419 {
420 name: "Viacoin",
421 onSelect: function() {
422 network = bitcoin.networks.viacoin;
423 DOM.bip44coin.val(14);
424 },
425 },
426 {
427 name: "Viacoin Testnet",
428 onSelect: function() {
429 network = bitcoin.networks.viacointestnet;
430 DOM.bip44coin.val(1);
431 },
432 },
433 {
434 name: "Jumbucks",
435 onSelect: function() {
436 network = bitcoin.networks.jumbucks;
437 DOM.bip44coin.val(26);
438 },
439 },
440 {
441 name: "CLAM",
442 onSelect: function() {
443 network = bitcoin.networks.clam;
444 DOM.bip44coin.val(23);
445 },
446 },
447 ]
448
449 init();
450
451})();