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