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