From efe41586705d3eba480da31bb1002c4e54e73ef0 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Tue, 23 Aug 2016 10:31:39 +1000 Subject: [PATCH] BIP32 Root Key can be specified by user --- bip39-standalone.html | 87 +++++++++++++++++++++++++++++++++++++------ src/index.html | 2 +- src/js/index.js | 85 ++++++++++++++++++++++++++++++++++++------ 3 files changed, 150 insertions(+), 24 deletions(-) diff --git a/bip39-standalone.html b/bip39-standalone.html index b1fe90e..9d0de5e 100644 --- a/bip39-standalone.html +++ b/bip39-standalone.html @@ -109,7 +109,7 @@
- +
@@ -14830,6 +14830,7 @@ var Mnemonic = function(language) { var showPrivKey = true; var phraseChangeTimeoutEvent = null; + var rootKeyChangedTimeoutEvent = null; var DOM = {}; DOM.network = $(".network"); @@ -14868,12 +14869,13 @@ var Mnemonic = function(language) { DOM.passphrase.on("input", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); - DOM.bip32path.on("input", delayedPhraseChanged); - DOM.bip44purpose.on("input", delayedPhraseChanged); - DOM.bip44coin.on("input", delayedPhraseChanged); - DOM.bip44account.on("input", delayedPhraseChanged); - DOM.bip44change.on("input", delayedPhraseChanged); - DOM.tab.on("click", delayedPhraseChanged); + DOM.rootKey.on("input", delayedRootKeyChanged); + DOM.bip32path.on("input", calcForDerivationPath); + DOM.bip44purpose.on("input", calcForDerivationPath); + DOM.bip44coin.on("input", calcForDerivationPath); + DOM.bip44account.on("input", calcForDerivationPath); + DOM.bip44change.on("input", calcForDerivationPath); + DOM.tab.on("shown.bs.tab", calcForDerivationPath); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); @@ -14888,7 +14890,7 @@ var Mnemonic = function(language) { function networkChanged(e) { var network = e.target.value; networks[network].onSelect(); - delayedPhraseChanged(); + displayBip32Info(); } function delayedPhraseChanged() { @@ -14905,12 +14907,57 @@ var Mnemonic = function(language) { hideValidationError(); // Get the mnemonic phrase var phrase = DOM.phrase.val(); - var passphrase = DOM.passphrase.val(); var errorText = findPhraseErrors(phrase); if (errorText) { showValidationError(errorText); return; } + // Calculate and display + var passphrase = DOM.passphrase.val(); + calcBip32RootKeyFromSeed(phrase, passphrase); + calcForDerivationPath(); + hidePending(); + } + + function delayedRootKeyChanged() { + // Warn if there is an existing mnemonic or passphrase. + if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) { + if (!confirm("This will clear existing mnemonic and passphrase")) { + DOM.rootKey.val(bip32RootKey); + return + } + } + hideValidationError(); + showPending(); + // Clear existing mnemonic and passphrase + DOM.phrase.val(""); + DOM.passphrase.val(""); + seed = null; + if (rootKeyChangedTimeoutEvent != null) { + clearTimeout(rootKeyChangedTimeoutEvent); + } + rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400); + } + + function rootKeyChanged() { + showPending(); + hideValidationError(); + // Validate the root key TODO + var rootKeyBase58 = DOM.rootKey.val(); + var errorText = validateRootKey(rootKeyBase58); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + calcBip32RootKeyFromBase58(rootKeyBase58); + calcForDerivationPath(); + hidePending(); + } + + function calcForDerivationPath() { + showPending(); + hideValidationError(); // Get the derivation path var derivationPath = getDerivationPath(); var errorText = findDerivationPathErrors(derivationPath); @@ -14918,8 +14965,7 @@ var Mnemonic = function(language) { showValidationError(errorText); return; } - // Calculate and display - calcBip32Seed(phrase, passphrase, derivationPath); + calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } @@ -14966,9 +15012,16 @@ var Mnemonic = function(language) { return words; } - function calcBip32Seed(phrase, passphrase, path) { + function calcBip32RootKeyFromSeed(phrase, passphrase) { seed = mnemonic.toSeed(phrase, passphrase); bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network); + } + + function calcBip32RootKeyFromBase58(rootKeyBase58) { + bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58); + } + + function calcBip32ExtendedKey(path) { bip32ExtendedKey = bip32RootKey; // Derive the key from the path var pathBits = path.split("/"); @@ -15031,6 +15084,16 @@ var Mnemonic = function(language) { return false; } + function validateRootKey(rootKeyBase58) { + try { + bitcoin.HDNode.fromBase58(rootKeyBase58); + } + catch (e) { + return "Invalid root key"; + } + return ""; + } + function getDerivationPath() { if (DOM.bip44tab.hasClass("active")) { var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44); diff --git a/src/index.html b/src/index.html index 6708675..fcdf109 100644 --- a/src/index.html +++ b/src/index.html @@ -105,7 +105,7 @@
- +
diff --git a/src/js/index.js b/src/js/index.js index f3582b0..88e891c 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -12,6 +12,7 @@ var showPrivKey = true; var phraseChangeTimeoutEvent = null; + var rootKeyChangedTimeoutEvent = null; var DOM = {}; DOM.network = $(".network"); @@ -50,12 +51,13 @@ DOM.passphrase.on("input", delayedPhraseChanged); DOM.generate.on("click", generateClicked); DOM.more.on("click", showMore); - DOM.bip32path.on("input", delayedPhraseChanged); - DOM.bip44purpose.on("input", delayedPhraseChanged); - DOM.bip44coin.on("input", delayedPhraseChanged); - DOM.bip44account.on("input", delayedPhraseChanged); - DOM.bip44change.on("input", delayedPhraseChanged); - DOM.tab.on("click", delayedPhraseChanged); + DOM.rootKey.on("input", delayedRootKeyChanged); + DOM.bip32path.on("input", calcForDerivationPath); + DOM.bip44purpose.on("input", calcForDerivationPath); + DOM.bip44coin.on("input", calcForDerivationPath); + DOM.bip44account.on("input", calcForDerivationPath); + DOM.bip44change.on("input", calcForDerivationPath); + DOM.tab.on("shown.bs.tab", calcForDerivationPath); DOM.indexToggle.on("click", toggleIndexes); DOM.addressToggle.on("click", toggleAddresses); DOM.privateKeyToggle.on("click", togglePrivateKeys); @@ -70,7 +72,7 @@ function networkChanged(e) { var network = e.target.value; networks[network].onSelect(); - delayedPhraseChanged(); + displayBip32Info(); } function delayedPhraseChanged() { @@ -87,12 +89,57 @@ hideValidationError(); // Get the mnemonic phrase var phrase = DOM.phrase.val(); - var passphrase = DOM.passphrase.val(); var errorText = findPhraseErrors(phrase); if (errorText) { showValidationError(errorText); return; } + // Calculate and display + var passphrase = DOM.passphrase.val(); + calcBip32RootKeyFromSeed(phrase, passphrase); + calcForDerivationPath(); + hidePending(); + } + + function delayedRootKeyChanged() { + // Warn if there is an existing mnemonic or passphrase. + if (DOM.phrase.val().length > 0 || DOM.passphrase.val().length > 0) { + if (!confirm("This will clear existing mnemonic and passphrase")) { + DOM.rootKey.val(bip32RootKey); + return + } + } + hideValidationError(); + showPending(); + // Clear existing mnemonic and passphrase + DOM.phrase.val(""); + DOM.passphrase.val(""); + seed = null; + if (rootKeyChangedTimeoutEvent != null) { + clearTimeout(rootKeyChangedTimeoutEvent); + } + rootKeyChangedTimeoutEvent = setTimeout(rootKeyChanged, 400); + } + + function rootKeyChanged() { + showPending(); + hideValidationError(); + // Validate the root key TODO + var rootKeyBase58 = DOM.rootKey.val(); + var errorText = validateRootKey(rootKeyBase58); + if (errorText) { + showValidationError(errorText); + return; + } + // Calculate and display + calcBip32RootKeyFromBase58(rootKeyBase58); + calcForDerivationPath(); + hidePending(); + } + + function calcForDerivationPath() { + showPending(); + hideValidationError(); // Get the derivation path var derivationPath = getDerivationPath(); var errorText = findDerivationPathErrors(derivationPath); @@ -100,8 +147,7 @@ showValidationError(errorText); return; } - // Calculate and display - calcBip32Seed(phrase, passphrase, derivationPath); + calcBip32ExtendedKey(derivationPath); displayBip32Info(); hidePending(); } @@ -148,9 +194,16 @@ return words; } - function calcBip32Seed(phrase, passphrase, path) { + function calcBip32RootKeyFromSeed(phrase, passphrase) { seed = mnemonic.toSeed(phrase, passphrase); bip32RootKey = bitcoin.HDNode.fromSeedHex(seed, network); + } + + function calcBip32RootKeyFromBase58(rootKeyBase58) { + bip32RootKey = bitcoin.HDNode.fromBase58(rootKeyBase58); + } + + function calcBip32ExtendedKey(path) { bip32ExtendedKey = bip32RootKey; // Derive the key from the path var pathBits = path.split("/"); @@ -213,6 +266,16 @@ return false; } + function validateRootKey(rootKeyBase58) { + try { + bitcoin.HDNode.fromBase58(rootKeyBase58); + } + catch (e) { + return "Invalid root key"; + } + return ""; + } + function getDerivationPath() { if (DOM.bip44tab.hasClass("active")) { var purpose = parseIntNoNaN(DOM.bip44purpose.val(), 44); -- 2.41.0