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