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