]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blame - src/js/index.js
Addresses shown in table as they're calculated
[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
a19a5498
IC
38 DOM.phrase.on("input", delayedPhraseChanged);
39 DOM.passphrase.on("input", delayedPhraseChanged);
ebd8d4e8
IC
40 DOM.generate.on("click", generateClicked);
41 DOM.more.on("click", showMore);
a19a5498
IC
42 DOM.bip32path.on("input", bip32Changed);
43 DOM.bip44purpose.on("input", bip44Changed);
44 DOM.bip44coin.on("input", bip44Changed);
45 DOM.bip44account.on("input", bip44Changed);
46 DOM.bip44change.on("input", bip44Changed);
ebd8d4e8
IC
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);
bade1504 167 bip32RootKey = Bitcoin.HDNode.fromSeedHex(seed, network);
ebd8d4e8
IC
168 bip32ExtendedKey = bip32RootKey;
169 // Derive the key from the path
170 var pathBits = path.split("/");
171 for (var i=0; i<pathBits.length; i++) {
172 var bit = pathBits[i];
173 var index = parseInt(bit);
174 if (isNaN(index)) {
175 continue;
176 }
177 var hardened = bit[bit.length-1] == "'";
178 if (hardened) {
179 bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
180 }
181 else {
182 bip32ExtendedKey = bip32ExtendedKey.derive(index);
183 }
184 }
185 }
186
187 function showValidationError(errorText) {
188 DOM.feedback
189 .text(errorText)
190 .show();
191 }
192
193 function hideValidationError() {
194 DOM.feedback
195 .text("")
196 .hide();
197 }
198
199 function findPhraseErrors(phrase) {
200 // TODO make this right
201 // Preprocess the words
783981de 202 phrase = mnemonic.normalizeString(phrase);
ebd8d4e8
IC
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++) {
a8c45487
IC
242 var index = i + start;
243 new TableRow(index);
ebd8d4e8
IC
244 }
245 }
246
a8c45487
IC
247 function TableRow(index) {
248
249 function init() {
250 calculateValues();
251 }
252
253 function calculateValues() {
254 setTimeout(function() {
255 var key = bip32ExtendedKey.derive(index);
256 var address = key.getAddress().toString();
257 var privkey = key.privKey.toWIF(network);
258 addAddressToList(index, address, privkey);
259 }, 50)
260 }
261
262 init();
263
264 }
265
ebd8d4e8
IC
266 function showMore() {
267 var start = DOM.addresses.children().length;
268 var rowsToAdd = parseInt(DOM.rowsToAdd.val());
269 if (isNaN(rowsToAdd)) {
270 rowsToAdd = 20;
271 DOM.rowsToAdd.val("20");
272 }
273 if (rowsToAdd > 200) {
274 var msg = "Generating " + rowsToAdd + " rows could take a while. ";
275 msg += "Do you want to continue?";
276 if (!confirm(msg)) {
277 return;
278 }
279 }
ebd8d4e8 280 displayAddresses(start, rowsToAdd);
ebd8d4e8
IC
281 }
282
283 function clearDisplay() {
284 clearAddressesList();
285 clearKey();
286 hideValidationError();
287 }
288
289 function clearAddressesList() {
290 DOM.addresses.empty();
291 }
292
293 function clearKey() {
294 DOM.rootKey.val("");
295 DOM.extendedPrivKey.val("");
296 DOM.extendedPubKey.val("");
297 }
298
299 function addAddressToList(index, address, privkey) {
300 var row = $(addressRowTemplate.html());
301 row.find(".index span").text(index);
302 row.find(".address span").text(address);
303 row.find(".privkey span").text(privkey);
304 DOM.addresses.append(row);
305 }
306
307 function hasStrongRandom() {
308 return 'crypto' in window && window['crypto'] !== null;
309 }
310
311 function disableForms() {
312 $("form").on("submit", function(e) {
313 e.preventDefault();
314 });
315 }
316
317 function setBip44DerivationPath() {
318 var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
319 var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
320 var account = parseIntNoNaN(DOM.bip44account.val(), 0);
321 var change = parseIntNoNaN(DOM.bip44change.val(), 0);
322 var path = "m/";
323 path += purpose + "'/";
324 path += coin + "'/";
325 path += account + "'/";
326 path += change;
327 DOM.bip44path.val(path);
328 }
329
330 function parseIntNoNaN(val, defaultVal) {
331 var v = parseInt(val);
332 if (isNaN(v)) {
333 return defaultVal;
334 }
335 return v;
336 }
337
338 function showPending() {
339 DOM.feedback
340 .text("Calculating...")
341 .show();
342 }
343
344 function hidePending() {
345 DOM.feedback
346 .text("")
347 .hide();
348 }
349
350 init();
351
352})();