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;
15 var rootKeyChangedTimeoutEvent
= null;
18 DOM
.network
= $(".network");
19 DOM
.phraseNetwork
= $("#network-phrase");
20 DOM
.phrase
= $(".phrase");
21 DOM
.passphrase
= $(".passphrase");
22 DOM
.generate
= $(".generate");
23 DOM
.seed
= $(".seed");
24 DOM
.rootKey
= $(".root-key");
25 DOM
.extendedPrivKey
= $(".extended-priv-key");
26 DOM
.extendedPubKey
= $(".extended-pub-key");
27 DOM
.bip32tab
= $("#bip32-tab");
28 DOM
.bip44tab
= $("#bip44-tab");
29 DOM
.bip32panel
= $("#bip32");
30 DOM
.bip44panel
= $("#bip44");
31 DOM
.bip32path
= $("#bip32-path");
32 DOM
.bip44path
= $("#bip44-path");
33 DOM
.bip44purpose
= $("#bip44 .purpose");
34 DOM
.bip44coin
= $("#bip44 .coin");
35 DOM
.bip44account
= $("#bip44 .account");
36 DOM
.bip44change
= $("#bip44 .change");
37 DOM
.strength
= $(".strength");
38 DOM
.hardenedAddresses
= $(".hardened-addresses");
39 DOM
.addresses
= $(".addresses");
40 DOM
.rowsToAdd
= $(".rows-to-add");
41 DOM
.more
= $(".more");
42 DOM
.feedback
= $(".feedback");
43 DOM
.tab
= $(".derivation-type a");
44 DOM
.indexToggle
= $(".index-toggle");
45 DOM
.addressToggle
= $(".address-toggle");
46 DOM
.privateKeyToggle
= $(".private-key-toggle");
50 DOM
.network
.on("change", networkChanged
);
51 DOM
.phrase
.on("input", delayedPhraseChanged
);
52 DOM
.passphrase
.on("input", delayedPhraseChanged
);
53 DOM
.generate
.on("click", generateClicked
);
54 DOM
.more
.on("click", showMore
);
55 DOM
.rootKey
.on("input", delayedRootKeyChanged
);
56 DOM
.bip32path
.on("input", calcForDerivationPath
);
57 DOM
.bip44purpose
.on("input", calcForDerivationPath
);
58 DOM
.bip44coin
.on("input", calcForDerivationPath
);
59 DOM
.bip44account
.on("input", calcForDerivationPath
);
60 DOM
.bip44change
.on("input", calcForDerivationPath
);
61 DOM
.tab
.on("shown.bs.tab", calcForDerivationPath
);
62 DOM
.hardenedAddresses
.on("change", calcForDerivationPath
);
63 DOM
.indexToggle
.on("click", toggleIndexes
);
64 DOM
.addressToggle
.on("click", toggleAddresses
);
65 DOM
.privateKeyToggle
.on("click", togglePrivateKeys
);
68 hideValidationError();
69 populateNetworkSelect();
74 function networkChanged(e
) {
75 var networkIndex
= e
.target
.value
;
76 networks
[networkIndex
].onSelect();
85 function delayedPhraseChanged() {
86 hideValidationError();
88 if (phraseChangeTimeoutEvent
!= null) {
89 clearTimeout(phraseChangeTimeoutEvent
);
91 phraseChangeTimeoutEvent
= setTimeout(phraseChanged
, 400);
94 function phraseChanged() {
96 hideValidationError();
97 // Get the mnemonic phrase
98 var phrase
= DOM
.phrase
.val();
99 var errorText
= findPhraseErrors(phrase
);
101 showValidationError(errorText
);
104 // Calculate and display
105 var passphrase
= DOM
.passphrase
.val();
106 calcBip32RootKeyFromSeed(phrase
, passphrase
);
107 calcForDerivationPath();
111 function delayedRootKeyChanged() {
112 // Warn if there is an existing mnemonic or passphrase.
113 if (DOM
.phrase
.val().length
> 0 || DOM
.passphrase
.val().length
> 0) {
114 if (!confirm("This will clear existing mnemonic and passphrase")) {
115 DOM
.rootKey
.val(bip32RootKey
);
119 hideValidationError();
121 // Clear existing mnemonic and passphrase
123 DOM
.passphrase
.val("");
125 if (rootKeyChangedTimeoutEvent
!= null) {
126 clearTimeout(rootKeyChangedTimeoutEvent
);
128 rootKeyChangedTimeoutEvent
= setTimeout(rootKeyChanged
, 400);
131 function rootKeyChanged() {
133 hideValidationError();
134 // Validate the root key TODO
135 var rootKeyBase58
= DOM
.rootKey
.val();
136 var errorText
= validateRootKey(rootKeyBase58
);
138 showValidationError(errorText
);
141 // Calculate and display
142 calcBip32RootKeyFromBase58(rootKeyBase58
);
143 calcForDerivationPath();
147 function calcForDerivationPath() {
149 hideValidationError();
150 // Get the derivation path
151 var derivationPath
= getDerivationPath();
152 var errorText
= findDerivationPathErrors(derivationPath
);
154 showValidationError(errorText
);
157 calcBip32ExtendedKey(derivationPath
);
162 function generateClicked() {
165 setTimeout(function() {
166 var phrase
= generateRandomPhrase();
174 function toggleIndexes() {
175 showIndex
= !showIndex
;
176 $("td.index span").toggleClass("invisible");
179 function toggleAddresses() {
180 showAddress
= !showAddress
;
181 $("td.address span").toggleClass("invisible");
184 function togglePrivateKeys() {
185 showPrivKey
= !showPrivKey
;
186 $("td.privkey span").toggleClass("invisible");
191 function generateRandomPhrase() {
192 if (!hasStrongRandom()) {
193 var errorText
= "This browser does not support strong randomness";
194 showValidationError(errorText
);
197 var numWords
= parseInt(DOM
.strength
.val());
198 var strength
= numWords
/ 3 * 32;
199 var words
= mnemonic
.generate(strength
);
200 DOM
.phrase
.val(words
);
204 function calcBip32RootKeyFromSeed(phrase
, passphrase
) {
205 seed
= mnemonic
.toSeed(phrase
, passphrase
);
206 bip32RootKey
= bitcoin
.HDNode
.fromSeedHex(seed
, network
);
209 function calcBip32RootKeyFromBase58(rootKeyBase58
) {
210 bip32RootKey
= bitcoin
.HDNode
.fromBase58(rootKeyBase58
, network
);
213 function calcBip32ExtendedKey(path
) {
214 bip32ExtendedKey
= bip32RootKey
;
215 // Derive the key from the path
216 var pathBits
= path
.split("/");
217 for (var i
=0; i
<pathBits
.length
; i
++) {
218 var bit
= pathBits
[i
];
219 var index
= parseInt(bit
);
223 var hardened
= bit
[bit
.length
-1] == "'";
225 bip32ExtendedKey
= bip32ExtendedKey
.deriveHardened(index
);
228 bip32ExtendedKey
= bip32ExtendedKey
.derive(index
);
233 function showValidationError(errorText
) {
239 function hideValidationError() {
245 function findPhraseErrors(phrase
) {
246 // TODO make this right
247 // Preprocess the words
248 phrase
= mnemonic
.normalizeString(phrase
);
249 var parts
= phrase
.split(" ");
251 for (var i
=0; i
<parts
.length
; i
++) {
253 if (part
.length
> 0) {
254 // TODO check that lowercasing is always valid to do
255 proper
.push(part
.toLowerCase());
258 var properPhrase
= proper
.join(' ');
260 for (var i
=0; i
<proper
.length
; i
++) {
261 var word
= proper
[i
];
262 if (WORDLISTS
["english"].indexOf(word
) == -1) {
263 console
.log("Finding closest match to " + word
);
264 var nearestWord
= findNearestWord(word
);
265 return word
+ " not in wordlist, did you mean " + nearestWord
+ "?";
268 // Check the words are valid
269 var isValid
= mnemonic
.check(properPhrase
);
271 return "Invalid mnemonic";
276 function validateRootKey(rootKeyBase58
) {
278 bitcoin
.HDNode
.fromBase58(rootKeyBase58
);
281 return "Invalid root key";
286 function getDerivationPath() {
287 if (DOM
.bip44tab
.hasClass("active")) {
288 var purpose
= parseIntNoNaN(DOM
.bip44purpose
.val(), 44);
289 var coin
= parseIntNoNaN(DOM
.bip44coin
.val(), 0);
290 var account
= parseIntNoNaN(DOM
.bip44account
.val(), 0);
291 var change
= parseIntNoNaN(DOM
.bip44change
.val(), 0);
293 path
+= purpose
+ "'/";
295 path
+= account
+ "'/";
297 DOM
.bip44path
.val(path
);
298 var derivationPath
= DOM
.bip44path
.val();
299 console
.log("Using derivation path from BIP44 tab: " + derivationPath
);
300 return derivationPath
;
302 else if (DOM
.bip32tab
.hasClass("active")) {
303 var derivationPath
= DOM
.bip32path
.val();
304 console
.log("Using derivation path from BIP32 tab: " + derivationPath
);
305 return derivationPath
;
308 console
.log("Unknown derivation path");
312 function findDerivationPathErrors(path
) {
313 // TODO is not perfect but is better than nothing
315 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#test-vectors
317 // https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#extended-keys
318 var maxDepth
= 255; // TODO verify this!!
319 var maxIndexValue
= Math
.pow(2, 31); // TODO verify this!!
320 if (path
[0] != "m") {
321 return "First character must be 'm'";
323 if (path
.length
> 1) {
324 if (path
[1] != "/") {
325 return "Separator must be '/'";
327 var indexes
= path
.split("/");
328 if (indexes
.length
> maxDepth
) {
329 return "Derivation depth is " + indexes
.length
+ ", must be less than " + maxDepth
;
331 for (var depth
= 1; depth
<indexes
.length
; depth
++) {
332 var index
= indexes
[depth
];
333 var invalidChars
= index
.replace(/^[0-9]+'?$/g, "")
334 if (invalidChars
.length
> 0) {
335 return "Invalid characters " + invalidChars
+ " found at depth " + depth
;
337 var indexValue
= parseInt(index
.replace("'", ""));
339 return "Invalid number at depth " + depth
;
341 if (indexValue
> maxIndexValue
) {
342 return "Value of " + indexValue
+ " at depth " + depth
+ " must be less than " + maxIndexValue
;
349 function displayBip32Info() {
352 var rootKey
= bip32RootKey
.toBase58();
353 DOM
.rootKey
.val(rootKey
);
354 var extendedPrivKey
= bip32ExtendedKey
.toBase58();
355 DOM
.extendedPrivKey
.val(extendedPrivKey
);
356 var extendedPubKey
= bip32ExtendedKey
.toBase58(false);
357 DOM
.extendedPubKey
.val(extendedPubKey
);
358 // Display the addresses and privkeys
359 clearAddressesList();
360 displayAddresses(0, 20);
363 function displayAddresses(start
, total
) {
364 for (var i
=0; i
<total
; i
++) {
365 var index
= i
+ start
;
370 function TableRow(index
) {
372 var useHardenedAddresses
= DOM
.hardenedAddresses
.prop("checked");
378 function calculateValues() {
379 setTimeout(function() {
381 if (useHardenedAddresses
) {
382 key
= bip32ExtendedKey
.deriveHardened(index
);
385 key
= bip32ExtendedKey
.derive(index
);
387 var address
= key
.getAddress().toString();
388 var privkey
= key
.privKey
.toWIF(network
);
389 var indexText
= getDerivationPath() + "/" + index
;
390 if (useHardenedAddresses
) {
391 indexText
= indexText
+ "'";
393 addAddressToList(indexText
, address
, privkey
);
401 function showMore() {
402 var start
= DOM
.addresses
.children().length
;
403 var rowsToAdd
= parseInt(DOM
.rowsToAdd
.val());
404 if (isNaN(rowsToAdd
)) {
406 DOM
.rowsToAdd
.val("20");
408 if (rowsToAdd
> 200) {
409 var msg
= "Generating " + rowsToAdd
+ " rows could take a while. ";
410 msg
+= "Do you want to continue?";
415 displayAddresses(start
, rowsToAdd
);
418 function clearDisplay() {
419 clearAddressesList();
421 hideValidationError();
424 function clearAddressesList() {
425 DOM
.addresses
.empty();
428 function clearKey() {
430 DOM
.extendedPrivKey
.val("");
431 DOM
.extendedPubKey
.val("");
434 function addAddressToList(indexText
, address
, privkey
) {
435 var row
= $(addressRowTemplate
.html());
437 var indexCell
= row
.find(".index span");
438 var addressCell
= row
.find(".address span");
439 var privkeyCell
= row
.find(".privkey span");
441 indexCell
.text(indexText
);
442 addressCell
.text(address
);
443 privkeyCell
.text(privkey
);
446 indexCell
.addClass("invisible");
449 addressCell
.addClass("invisible");
452 privkeyCell
.addClass("invisible");
454 DOM
.addresses
.append(row
);
457 function hasStrongRandom() {
458 return 'crypto' in window
&& window
['crypto'] !== null;
461 function disableForms() {
462 $("form").on("submit", function(e
) {
467 function parseIntNoNaN(val
, defaultVal
) {
468 var v
= parseInt(val
);
475 function showPending() {
477 .text("Calculating...")
481 function findNearestWord(word
) {
482 var words
= WORDLISTS
["english"];
483 var minDistance
= 99;
484 var closestWord
= words
[0];
485 for (var i
=0; i
<words
.length
; i
++) {
486 var comparedTo
= words
[i
];
487 var distance
= Levenshtein
.get(word
, comparedTo
);
488 if (distance
< minDistance
) {
489 closestWord
= comparedTo
;
490 minDistance
= distance
;
496 function hidePending() {
502 function populateNetworkSelect() {
503 for (var i
=0; i
<networks
.length
; i
++) {
504 var network
= networks
[i
];
505 var option
= $("<option>");
506 option
.attr("value", i
);
507 option
.text(network
.name
);
508 DOM
.phraseNetwork
.append(option
);
515 onSelect: function() {
516 network
= bitcoin
.networks
.bitcoin
;
517 DOM
.bip44coin
.val(0);
521 name: "Bitcoin Testnet",
522 onSelect: function() {
523 network
= bitcoin
.networks
.testnet
;
524 DOM
.bip44coin
.val(1);
529 onSelect: function() {
530 network
= bitcoin
.networks
.litecoin
;
531 DOM
.bip44coin
.val(2);
536 onSelect: function() {
537 network
= bitcoin
.networks
.dogecoin
;
538 DOM
.bip44coin
.val(3);
543 onSelect: function() {
544 network
= bitcoin
.networks
.shadow
;
545 DOM
.bip44coin
.val(35);
549 name: "ShadowCash Testnet",
550 onSelect: function() {
551 network
= bitcoin
.networks
.shadowtn
;
552 DOM
.bip44coin
.val(1);
557 onSelect: function() {
558 network
= bitcoin
.networks
.viacoin
;
559 DOM
.bip44coin
.val(14);
563 name: "Viacoin Testnet",
564 onSelect: function() {
565 network
= bitcoin
.networks
.viacointestnet
;
566 DOM
.bip44coin
.val(1);
571 onSelect: function() {
572 network
= bitcoin
.networks
.jumbucks
;
573 DOM
.bip44coin
.val(26);
578 onSelect: function() {
579 network
= bitcoin
.networks
.clam
;
580 DOM
.bip44coin
.val(23);
585 onSelect: function() {
586 network
= bitcoin
.networks
.dash
;
587 DOM
.bip44coin
.val(5);