mirror of
https://github.com/OneKeyHQ/bip39.git
synced 2026-04-05 18:43:47 +00:00
357 lines
10 KiB
JavaScript
357 lines
10 KiB
JavaScript
(function() {
|
|
|
|
var mnemonic = new Mnemonic("english");
|
|
var bip32RootKey = null;
|
|
var bip32ExtendedKey = null;
|
|
var network = Bitcoin.networks.bitcoin;
|
|
var addressRowTemplate = $("#address-row-template");
|
|
|
|
var phraseChangeTimeoutEvent = null;
|
|
|
|
var DOM = {};
|
|
DOM.phrase = $(".phrase");
|
|
DOM.passphrase = $(".passphrase");
|
|
DOM.generate = $(".generate");
|
|
DOM.rootKey = $(".root-key");
|
|
DOM.extendedPrivKey = $(".extended-priv-key");
|
|
DOM.extendedPubKey = $(".extended-pub-key");
|
|
DOM.bip32path = $("#bip32-path");
|
|
DOM.bip44path = $("#bip44-path");
|
|
DOM.bip44purpose = $("#bip44 .purpose");
|
|
DOM.bip44coin = $("#bip44 .coin");
|
|
DOM.bip44account = $("#bip44 .account");
|
|
DOM.bip44change = $("#bip44 .change");
|
|
DOM.strength = $(".strength");
|
|
DOM.addresses = $(".addresses");
|
|
DOM.rowsToAdd = $(".rows-to-add");
|
|
DOM.more = $(".more");
|
|
DOM.feedback = $(".feedback");
|
|
DOM.tab = $(".derivation-type a");
|
|
DOM.indexToggle = $(".index-toggle");
|
|
DOM.addressToggle = $(".address-toggle");
|
|
DOM.privateKeyToggle = $(".private-key-toggle");
|
|
|
|
var derivationPath = DOM.bip44path.val();
|
|
var currentPhrase = DOM.phrase.val();
|
|
var currentPassphrase = DOM.passphrase.val();
|
|
|
|
function init() {
|
|
// Events
|
|
DOM.phrase.on("keyup", delayedPhraseChanged);
|
|
DOM.passphrase.on("keyup", delayedPhraseChanged);
|
|
DOM.generate.on("click", generateClicked);
|
|
DOM.more.on("click", showMore);
|
|
DOM.bip32path.on("keyup", bip32Changed);
|
|
DOM.bip44purpose.on("keyup", bip44Changed);
|
|
DOM.bip44coin.on("keyup", bip44Changed);
|
|
DOM.bip44account.on("keyup", bip44Changed);
|
|
DOM.bip44change.on("keyup", bip44Changed);
|
|
DOM.tab.on("click", tabClicked);
|
|
DOM.indexToggle.on("click", toggleIndexes);
|
|
DOM.addressToggle.on("click", toggleAddresses);
|
|
DOM.privateKeyToggle.on("click", togglePrivateKeys);
|
|
disableForms();
|
|
hidePending();
|
|
hideValidationError();
|
|
}
|
|
|
|
// Event handlers
|
|
|
|
function delayedPhraseChanged() {
|
|
if (!hasChanged()) {
|
|
return;
|
|
}
|
|
hideValidationError();
|
|
showPending();
|
|
if (phraseChangeTimeoutEvent != null) {
|
|
clearTimeout(phraseChangeTimeoutEvent);
|
|
}
|
|
phraseChangeTimeoutEvent = setTimeout(phraseChanged, 400);
|
|
}
|
|
|
|
function phraseChanged() {
|
|
showPending();
|
|
hideValidationError();
|
|
// Get the mnemonic phrase
|
|
var phrase = DOM.phrase.val();
|
|
var passphrase = DOM.passphrase.val();
|
|
var errorText = findPhraseErrors(phrase);
|
|
if (errorText) {
|
|
showValidationError(errorText);
|
|
return;
|
|
}
|
|
// Get the derivation path
|
|
var errorText = findDerivationPathErrors();
|
|
if (errorText) {
|
|
showValidationError(errorText);
|
|
return;
|
|
}
|
|
// Calculate and display
|
|
calcBip32Seed(phrase, passphrase, derivationPath);
|
|
displayBip32Info();
|
|
hidePending();
|
|
// Set current state so we only update as needed
|
|
currentPhrase = phrase;
|
|
currentPassphrase = passphrase;
|
|
}
|
|
|
|
function generateClicked() {
|
|
clearDisplay();
|
|
showPending();
|
|
setTimeout(function() {
|
|
var phrase = generateRandomPhrase();
|
|
if (!phrase) {
|
|
return;
|
|
}
|
|
phraseChanged();
|
|
}, 50);
|
|
}
|
|
|
|
function tabClicked(e) {
|
|
var activePath = $(e.target.getAttribute("href") + " .path");
|
|
derivationPath = activePath.val();
|
|
derivationChanged();
|
|
}
|
|
|
|
function derivationChanged() {
|
|
hideValidationError();
|
|
showPending();
|
|
setTimeout(phraseChanged, 50);
|
|
}
|
|
|
|
function bip32Changed() {
|
|
derivationPath = DOM.bip32path.val();
|
|
derivationChanged();
|
|
}
|
|
|
|
function bip44Changed() {
|
|
setBip44DerivationPath();
|
|
derivationPath = DOM.bip44path.val();
|
|
derivationChanged();
|
|
}
|
|
|
|
function toggleIndexes() {
|
|
$("td.index span").toggleClass("invisible");
|
|
}
|
|
|
|
function toggleAddresses() {
|
|
$("td.address span").toggleClass("invisible");
|
|
}
|
|
|
|
function togglePrivateKeys() {
|
|
$("td.privkey span").toggleClass("invisible");
|
|
}
|
|
|
|
// Private methods
|
|
|
|
function generateRandomPhrase() {
|
|
if (!hasStrongRandom()) {
|
|
var errorText = "This browser does not support strong randomness";
|
|
showValidationError(errorText);
|
|
return;
|
|
}
|
|
var numWords = parseInt(DOM.strength.val());
|
|
// Check strength is an integer
|
|
if (isNaN(numWords)) {
|
|
DOM.strength.val("12");
|
|
numWords = 12;
|
|
}
|
|
// Check strength is a multiple of 32, if not round it down
|
|
if (numWords % 3 != 0) {
|
|
numWords = Math.floor(numWords / 3) * 3;
|
|
DOM.strength.val(numWords);
|
|
}
|
|
// Check strength is at least 32
|
|
if (numWords == 0) {
|
|
numWords = 3;
|
|
DOM.strength.val(numWords);
|
|
}
|
|
var strength = numWords / 3 * 32;
|
|
var words = mnemonic.generate(strength);
|
|
DOM.phrase.val(words);
|
|
return words;
|
|
}
|
|
|
|
function calcBip32Seed(phrase, passphrase, path) {
|
|
var seed = mnemonic.toSeed(phrase, passphrase);
|
|
var seedHash = Bitcoin.crypto.sha256(seed).toString("hex");
|
|
bip32RootKey = Bitcoin.HDNode.fromSeedHex(seedHash, network);
|
|
bip32ExtendedKey = bip32RootKey;
|
|
// Derive the key from the path
|
|
var pathBits = path.split("/");
|
|
for (var i=0; i<pathBits.length; i++) {
|
|
var bit = pathBits[i];
|
|
var index = parseInt(bit);
|
|
if (isNaN(index)) {
|
|
continue;
|
|
}
|
|
var hardened = bit[bit.length-1] == "'";
|
|
if (hardened) {
|
|
bip32ExtendedKey = bip32ExtendedKey.deriveHardened(index);
|
|
}
|
|
else {
|
|
bip32ExtendedKey = bip32ExtendedKey.derive(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
function showValidationError(errorText) {
|
|
DOM.feedback
|
|
.text(errorText)
|
|
.show();
|
|
}
|
|
|
|
function hideValidationError() {
|
|
DOM.feedback
|
|
.text("")
|
|
.hide();
|
|
}
|
|
|
|
function findPhraseErrors(phrase) {
|
|
// TODO make this right
|
|
// Preprocess the words
|
|
var parts = phrase.split(" ");
|
|
var proper = [];
|
|
for (var i=0; i<parts.length; i++) {
|
|
var part = parts[i];
|
|
if (part.length > 0) {
|
|
// TODO check that lowercasing is always valid to do
|
|
proper.push(part.toLowerCase());
|
|
}
|
|
}
|
|
// TODO some levenstein on the words
|
|
var properPhrase = proper.join(' ');
|
|
// Check the words are valid
|
|
var isValid = mnemonic.check(properPhrase);
|
|
if (!isValid) {
|
|
return "Invalid mnemonic";
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function findDerivationPathErrors(path) {
|
|
// TODO
|
|
return false;
|
|
}
|
|
|
|
function displayBip32Info() {
|
|
// Display the key
|
|
var rootKey = bip32RootKey.toBase58();
|
|
DOM.rootKey.val(rootKey);
|
|
var extendedPrivKey = bip32ExtendedKey.toBase58();
|
|
DOM.extendedPrivKey.val(extendedPrivKey);
|
|
var extendedPubKey = bip32ExtendedKey.toBase58(false);
|
|
DOM.extendedPubKey.val(extendedPubKey);
|
|
// Display the addresses and privkeys
|
|
clearAddressesList();
|
|
displayAddresses(0, 20);
|
|
}
|
|
|
|
function displayAddresses(start, total) {
|
|
for (var i=0; i<total; i++) {
|
|
var index = i+ start;
|
|
var key = bip32ExtendedKey.derive(index);
|
|
var address = key.getAddress().toString();
|
|
var privkey = key.privKey.toWIF();
|
|
addAddressToList(index, address, privkey);
|
|
}
|
|
}
|
|
|
|
function showMore() {
|
|
var start = DOM.addresses.children().length;
|
|
var rowsToAdd = parseInt(DOM.rowsToAdd.val());
|
|
if (isNaN(rowsToAdd)) {
|
|
rowsToAdd = 20;
|
|
DOM.rowsToAdd.val("20");
|
|
}
|
|
if (rowsToAdd > 200) {
|
|
var msg = "Generating " + rowsToAdd + " rows could take a while. ";
|
|
msg += "Do you want to continue?";
|
|
if (!confirm(msg)) {
|
|
return;
|
|
}
|
|
}
|
|
showPending();
|
|
setTimeout(function() {
|
|
displayAddresses(start, rowsToAdd);
|
|
hidePending();
|
|
}, 50);
|
|
}
|
|
|
|
function clearDisplay() {
|
|
clearAddressesList();
|
|
clearKey();
|
|
hideValidationError();
|
|
}
|
|
|
|
function clearAddressesList() {
|
|
DOM.addresses.empty();
|
|
}
|
|
|
|
function clearKey() {
|
|
DOM.rootKey.val("");
|
|
DOM.extendedPrivKey.val("");
|
|
DOM.extendedPubKey.val("");
|
|
}
|
|
|
|
function addAddressToList(index, address, privkey) {
|
|
var row = $(addressRowTemplate.html());
|
|
row.find(".index span").text(index);
|
|
row.find(".address span").text(address);
|
|
row.find(".privkey span").text(privkey);
|
|
DOM.addresses.append(row);
|
|
}
|
|
|
|
function hasStrongRandom() {
|
|
return 'crypto' in window && window['crypto'] !== null;
|
|
}
|
|
|
|
function disableForms() {
|
|
$("form").on("submit", function(e) {
|
|
e.preventDefault();
|
|
});
|
|
}
|
|
|
|
function setBip44DerivationPath() {
|
|
var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44);
|
|
var coin = parseIntNoNaN(DOM.bip44coin.val(), 0);
|
|
var account = parseIntNoNaN(DOM.bip44account.val(), 0);
|
|
var change = parseIntNoNaN(DOM.bip44change.val(), 0);
|
|
var path = "m/";
|
|
path += purpose + "'/";
|
|
path += coin + "'/";
|
|
path += account + "'/";
|
|
path += change;
|
|
DOM.bip44path.val(path);
|
|
}
|
|
|
|
function parseIntNoNaN(val, defaultVal) {
|
|
var v = parseInt(val);
|
|
if (isNaN(v)) {
|
|
return defaultVal;
|
|
}
|
|
return v;
|
|
}
|
|
|
|
function showPending() {
|
|
DOM.feedback
|
|
.text("Calculating...")
|
|
.show();
|
|
}
|
|
|
|
function hidePending() {
|
|
DOM.feedback
|
|
.text("")
|
|
.hide();
|
|
}
|
|
|
|
function hasChanged() {
|
|
var phraseChanged = DOM.phrase.val() != currentPhrase;
|
|
var passphraseChanged = DOM.passphrase.val() != currentPassphrase;
|
|
return phraseChanged || passphraseChanged;
|
|
}
|
|
|
|
init();
|
|
|
|
})();
|