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