]>
git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/blob - src/js/index.js
3 var mnemonic
= new Mnemonic("english");
5 var bip32RootKey
= null;
6 var bip32ExtendedKey
= null;
7 var network
= bitcoin
.networks
.bitcoin
;
8 var addressRowTemplate
= $("#address-row-template");
11 var showAddress
= true;
12 var showPrivKey
= true;
14 var phraseChangeTimeoutEvent
= null;
17 DOM
.network
= $(".network");
18 DOM
.phraseNetwork
= $("#network-phrase");
19 DOM
.phrase
= $(".phrase");
20 DOM
.passphrase
= $(".passphrase");
21 DOM
.generate
= $(".generate");
22 DOM
.seed
= $(".seed");
23 DOM
.rootKey
= $(".root-key");
24 DOM
.extendedPrivKey
= $(".extended-priv-key");
25 DOM
.extendedPubKey
= $(".extended-pub-key");
26 DOM
.bip32tab
= $("#bip32-tab");
27 DOM
.bip44tab
= $("#bip44-tab");
28 DOM
.bip32panel
= $("#bip32");
29 DOM
.bip44panel
= $("#bip44");
30 DOM
.bip32path
= $("#bip32-path");
31 DOM
.bip44path
= $("#bip44-path");
32 DOM
.bip44purpose
= $("#bip44 .purpose");
33 DOM
.bip44coin
= $("#bip44 .coin");
34 DOM
.bip44account
= $("#bip44 .account");
35 DOM
.bip44change
= $("#bip44 .change");
36 DOM
.strength
= $(".strength");
37 DOM
.addresses
= $(".addresses");
38 DOM
.rowsToAdd
= $(".rows-to-add");
39 DOM
.more
= $(".more");
40 DOM
.feedback
= $(".feedback");
41 DOM
.tab
= $(".derivation-type a");
42 DOM
.indexToggle
= $(".index-toggle");
43 DOM
.addressToggle
= $(".address-toggle");
44 DOM
.privateKeyToggle
= $(".private-key-toggle");
48 DOM
.network
.on("change", networkChanged
);
49 DOM
.phrase
.on("input", delayedPhraseChanged
);
50 DOM
.passphrase
.on("input", delayedPhraseChanged
);
51 DOM
.generate
.on("click", generateClicked
);
52 DOM
.more
.on("click", showMore
);
53 DOM
.bip32path
.on("input", delayedPhraseChanged
);
54 DOM
.bip44purpose
.on("input", delayedPhraseChanged
);
55 DOM
.bip44coin
.on("input", delayedPhraseChanged
);
56 DOM
.bip44account
.on("input", delayedPhraseChanged
);
57 DOM
.bip44change
.on("input", delayedPhraseChanged
);
58 DOM
.tab
.on("click", delayedPhraseChanged
);
59 DOM
.indexToggle
.on("click", toggleIndexes
);
60 DOM
.addressToggle
.on("click", toggleAddresses
);
61 DOM
.privateKeyToggle
.on("click", togglePrivateKeys
);
64 hideValidationError();
65 populateNetworkSelect();
70 function networkChanged(e
) {
71 var network
= e
.target
.value
;
72 networks
[network
].onSelect();
73 delayedPhraseChanged();
76 function delayedPhraseChanged() {
77 hideValidationError();
79 if (phraseChangeTimeoutEvent
!= null) {
80 clearTimeout(phraseChangeTimeoutEvent
);
82 phraseChangeTimeoutEvent
= setTimeout(phraseChanged
, 400);
85 function phraseChanged() {
87 hideValidationError();
88 // Get the mnemonic phrase
89 var phrase
= DOM
.phrase
.val();
90 var passphrase
= DOM
.passphrase
.val();
91 var errorText
= findPhraseErrors(phrase
);
93 showValidationError(errorText
);
96 // Get the derivation path
97 var derivationPath
= getDerivationPath();
98 var errorText
= findDerivationPathErrors(derivationPath
);
100 showValidationError(errorText
);
103 // Calculate and display
104 calcBip32Seed(phrase
, passphrase
, derivationPath
);
109 function generateClicked() {
112 setTimeout(function() {
113 var phrase
= generateRandomPhrase();
121 function toggleIndexes() {
122 showIndex
= !showIndex
;
123 $("td.index span").toggleClass("invisible");
126 function toggleAddresses() {
127 showAddress
= !showAddress
;
128 $("td.address span").toggleClass("invisible");
131 function togglePrivateKeys() {
132 showPrivKey
= !showPrivKey
;
133 $("td.privkey span").toggleClass("invisible");
138 function generateRandomPhrase() {
139 if (!hasStrongRandom()) {
140 var errorText
= "This browser does not support strong randomness";
141 showValidationError(errorText
);
144 var numWords
= parseInt(DOM
.strength
.val());
145 var strength
= numWords
/ 3 * 32;
146 var words
= mnemonic
.generate(strength
);
147 DOM
.phrase
.val(words
);
151 function calcBip32Seed(phrase
, passphrase
, path
) {
152 seed
= mnemonic
.toSeed(phrase
, passphrase
);
153 bip32RootKey
= bitcoin
.HDNode
.fromSeedHex(seed
, network
);
154 bip32ExtendedKey
= bip32RootKey
;
155 // Derive the key from the path
156 var pathBits
= path
.split("/");
157 for (var i
=0; i
<pathBits
.length
; i
++) {
158 var bit
= pathBits
[i
];
159 var index
= parseInt(bit
);
163 var hardened
= bit
[bit
.length
-1] == "'";
165 bip32ExtendedKey
= bip32ExtendedKey
.deriveHardened(index
);
168 bip32ExtendedKey
= bip32ExtendedKey
.derive(index
);
173 function showValidationError(errorText
) {
179 function hideValidationError() {
185 function findPhraseErrors(phrase
) {
186 // TODO make this right
187 // Preprocess the words
188 phrase
= mnemonic
.normalizeString(phrase
);
189 var parts
= phrase
.split(" ");
191 for (var i
=0; i
<parts
.length
; i
++) {
193 if (part
.length
> 0) {
194 // TODO check that lowercasing is always valid to do
195 proper
.push(part
.toLowerCase());
198 var properPhrase
= proper
.join(' ');
200 for (var i
=0; i
<proper
.length
; i
++) {
201 var word
= proper
[i
];
202 if (WORDLISTS
["english"].indexOf(word
) == -1) {
203 console
.log("Finding closest match to " + word
);
204 var nearestWord
= findNearestWord(word
);
205 return word
+ " not in wordlist, did you mean " + nearestWord
+ "?";
208 // Check the words are valid
209 var isValid
= mnemonic
.check(properPhrase
);
211 return "Invalid mnemonic";
216 function getDerivationPath() {
217 if (DOM
.bip44tab
.hasClass("active")) {
218 var purpose
= parseIntNoNaN(DOM
.bip44purpose
.val(), 44);
219 var coin
= parseIntNoNaN(DOM
.bip44coin
.val(), 0);
220 var account
= parseIntNoNaN(DOM
.bip44account
.val(), 0);
221 var change
= parseIntNoNaN(DOM
.bip44change
.val(), 0);
223 path
+= purpose
+ "'/";
225 path
+= account
+ "'/";
227 DOM
.bip44path
.val(path
);
228 var derivationPath
= DOM
.bip44path
.val();
229 console
.log("Using derivation path from BIP44 tab: " + derivationPath
);
230 return derivationPath
;
232 else if (DOM
.bip32tab
.hasClass("active")) {
233 var derivationPath
= DOM
.bip32path
.val();
234 console
.log("Using derivation path from BIP32 tab: " + derivationPath
);
235 return derivationPath
;
238 console
.log("Unknown derivation path");
242 function findDerivationPathErrors(path
) {
243 // TODO is not perfect but is better than nothing
245 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
247 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
248 var maxDepth
= 255; // TODO verify this!!
249 var maxIndexValue
= Math
.pow(2, 31); // TODO verify this!!
250 if (path
[0] != "m") {
251 return "First character must be 'm'";
253 if (path
.length
> 1) {
254 if (path
[1] != "/") {
255 return "Separator must be '/'";
257 var indexes
= path
.split("/");
258 if (indexes
.length
> maxDepth
) {
259 return "Derivation depth is " + indexes
.length
+ ", must be less than " + maxDepth
;
261 for (var depth
= 1; depth
<indexes
.length
; depth
++) {
262 var index
= indexes
[depth
];
263 var invalidChars
= index
.replace(/^[0-9]+'?$/g, "")
264 if (invalidChars
.length
> 0) {
265 return "Invalid characters " + invalidChars
+ " found at depth " + depth
;
267 var indexValue
= parseInt(index
.replace("'", ""));
269 return "Invalid number at depth " + depth
;
271 if (indexValue
> maxIndexValue
) {
272 return "Value of " + indexValue
+ " at depth " + depth
+ " must be less than " + maxIndexValue
;
279 function displayBip32Info() {
282 var rootKey
= bip32RootKey
.toBase58();
283 DOM
.rootKey
.val(rootKey
);
284 var extendedPrivKey
= bip32ExtendedKey
.toBase58();
285 DOM
.extendedPrivKey
.val(extendedPrivKey
);
286 var extendedPubKey
= bip32ExtendedKey
.toBase58(false);
287 DOM
.extendedPubKey
.val(extendedPubKey
);
288 // Display the addresses and privkeys
289 clearAddressesList();
290 displayAddresses(0, 20);
293 function displayAddresses(start
, total
) {
294 for (var i
=0; i
<total
; i
++) {
295 var index
= i
+ start
;
300 function TableRow(index
) {
306 function calculateValues() {
307 setTimeout(function() {
308 var key
= bip32ExtendedKey
.derive(index
);
309 var address
= key
.getAddress().toString();
310 var privkey
= key
.privKey
.toWIF(network
);
311 var indexText
= getDerivationPath() + "/" + index
;
312 addAddressToList(indexText
, address
, privkey
);
320 function showMore() {
321 var start
= DOM
.addresses
.children().length
;
322 var rowsToAdd
= parseInt(DOM
.rowsToAdd
.val());
323 if (isNaN(rowsToAdd
)) {
325 DOM
.rowsToAdd
.val("20");
327 if (rowsToAdd
> 200) {
328 var msg
= "Generating " + rowsToAdd
+ " rows could take a while. ";
329 msg
+= "Do you want to continue?";
334 displayAddresses(start
, rowsToAdd
);
337 function clearDisplay() {
338 clearAddressesList();
340 hideValidationError();
343 function clearAddressesList() {
344 DOM
.addresses
.empty();
347 function clearKey() {
349 DOM
.extendedPrivKey
.val("");
350 DOM
.extendedPubKey
.val("");
353 function addAddressToList(indexText
, address
, privkey
) {
354 var row
= $(addressRowTemplate
.html());
356 var indexCell
= row
.find(".index span");
357 var addressCell
= row
.find(".address span");
358 var privkeyCell
= row
.find(".privkey span");
360 indexCell
.text(indexText
);
361 addressCell
.text(address
);
362 privkeyCell
.text(privkey
);
365 indexCell
.addClass("invisible");
368 addressCell
.addClass("invisible");
371 privkeyCell
.addClass("invisible");
373 DOM
.addresses
.append(row
);
376 function hasStrongRandom() {
377 return 'crypto' in window
&& window
['crypto'] !== null;
380 function disableForms() {
381 $("form").on("submit", function(e
) {
386 function parseIntNoNaN(val
, defaultVal
) {
387 var v
= parseInt(val
);
394 function showPending() {
396 .text("Calculating...")
400 function findNearestWord(word
) {
401 var words
= WORDLISTS
["english"];
402 var minDistance
= 99;
403 var closestWord
= words
[0];
404 for (var i
=0; i
<words
.length
; i
++) {
405 var comparedTo
= words
[i
];
406 var distance
= Levenshtein
.get(word
, comparedTo
);
407 if (distance
< minDistance
) {
408 closestWord
= comparedTo
;
409 minDistance
= distance
;
415 function hidePending() {
421 function populateNetworkSelect() {
422 for (var i
=0; i
<networks
.length
; i
++) {
423 var network
= networks
[i
];
424 var option
= $("<option>");
425 option
.attr("value", i
);
426 option
.text(network
.name
);
427 DOM
.phraseNetwork
.append(option
);
434 onSelect: function() {
435 network
= bitcoin
.networks
.bitcoin
;
436 DOM
.bip44coin
.val(0);
440 name: "Bitcoin Testnet",
441 onSelect: function() {
442 network
= bitcoin
.networks
.testnet
;
443 DOM
.bip44coin
.val(1);
448 onSelect: function() {
449 network
= bitcoin
.networks
.litecoin
;
450 DOM
.bip44coin
.val(2);
455 onSelect: function() {
456 network
= bitcoin
.networks
.dogecoin
;
457 DOM
.bip44coin
.val(3);
462 onSelect: function() {
463 network
= bitcoin
.networks
.shadow
;
464 DOM
.bip44coin
.val(35);
468 name: "ShadowCash Testnet",
469 onSelect: function() {
470 network
= bitcoin
.networks
.shadowtn
;
471 DOM
.bip44coin
.val(1);
476 onSelect: function() {
477 network
= bitcoin
.networks
.viacoin
;
478 DOM
.bip44coin
.val(14);
482 name: "Viacoin Testnet",
483 onSelect: function() {
484 network
= bitcoin
.networks
.viacointestnet
;
485 DOM
.bip44coin
.val(1);
490 onSelect: function() {
491 network
= bitcoin
.networks
.jumbucks
;
492 DOM
.bip44coin
.val(26);
497 onSelect: function() {
498 network
= bitcoin
.networks
.clam
;
499 DOM
.bip44coin
.val(23);