<li id="bip44-tab" class="active">
<a href="#bip44" role="tab" data-toggle="tab">BIP44</a>
</li>
+ <li id="bip49-tab">
+ <a href="#bip49" role="tab" data-toggle="tab">BIP49</a>
+ </li>
</ul>
<div class="derivation-type tab-content">
<div id="bip44" class="tab-pane active">
</div>
</form>
</div>
+ <div id="bip49" class="tab-pane">
+ <form class="form-horizontal" role="form">
+ <br>
+ <div class="unavailable hidden">
+ <div class="form-group">
+ <div class="col-sm-2"></div>
+ <div class="col-sm-10">
+ <p data-translate>BIP49 is unavailable for this coin.</p>
+ </div>
+ </div>
+ </div>
+ <div class="available">
+ <div class="col-sm-2"></div>
+ <div class="col-sm-10">
+ <p data-translate-html>
+ For more info see the
+ <a href="https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki" target="_blank">BIP49 spec</a>.
+ </p>
+ </div>
+ <div class="form-group">
+ <label for="purpose" class="col-sm-2 control-label">
+ <a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#purpose" target="_blank" data-translate>Purpose</a>
+ </label>
+ <div class="col-sm-10">
+ <input id="purpose" type="text" class="purpose form-control" value="49" readonly>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="coin" class="col-sm-2 control-label">
+ <a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#registered-coin-types" target="_blank" data-translate>Coin</a>
+ </label>
+ <div class="col-sm-10">
+ <input id="coin" type="text" class="coin form-control" value="0" readonly>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="account" class="col-sm-2 control-label">
+ <a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account" target="_blank" data-translate>Account</a>
+ </label>
+ <div class="col-sm-10">
+ <input id="account" type="text" class="account form-control" value="0">
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="change" class="col-sm-2 control-label">
+ <a href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#change" target="_blank" data-translate>External / Internal</a>
+ </label>
+ <div class="col-sm-10">
+ <input id="change" type="text" class="change form-control" value="0">
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label">
+ </label>
+ <div class="col-sm-10">
+ <p data-translate>The account extended keys can be used for importing to most BIP49 compatible wallets.</p>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="account-xprv" class="col-sm-2 control-label">
+ <span data-translate>Account Extended Private Key</span>
+ </label>
+ <div class="col-sm-10">
+ <textarea id="account-xprv" type="text" class="account-xprv form-control" readonly data-show-qr></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="account-xpub" class="col-sm-2 control-label">
+ <span data-translate>Account Extended Public Key</span>
+ </label>
+ <div class="col-sm-10">
+ <textarea id="account-xpub" type="text" class="account-xpub form-control" readonly data-show-qr></textarea>
+ </div>
+ </div>
+ <div class="form-group">
+ <label class="col-sm-2 control-label">
+ </label>
+ <div class="col-sm-10">
+ <p data-translate>The BIP32 derivation path and extended keys are the basis for the derived addresses.</p>
+ </div>
+ </div>
+ <div class="form-group">
+ <label for="bip49-path" class="col-sm-2 control-label" data-translate>BIP32 Derivation Path</label>
+ <div class="col-sm-10">
+ <input id="bip49-path" type="text" class="path form-control" value="m/49'/0'/0'/0" readonly="readonly">
+ </div>
+ </div>
+ </div>
+ </form>
+ </div>
</div>
<form class="form-horizontal" role="form">
<div class="form-group">
wif: 0x85,
};
-bitcoin.networks.crown = {
+bitcoinjs.bitcoin.networks.crown = {
messagePrefix: "unused",
bip32: {
public: 0x0488b21e,
wif: 0x80,
};
-bitcoin.networks.dash = {
+bitcoinjs.bitcoin.networks.dash = {
messagePrefix: "unused",
bip32: {
public: 0x0488b21e,
DOM.extendedPubKey = $(".extended-pub-key");
DOM.bip32tab = $("#bip32-tab");
DOM.bip44tab = $("#bip44-tab");
+ DOM.bip49tab = $("#bip49-tab");
DOM.bip32panel = $("#bip32");
DOM.bip44panel = $("#bip44");
+ DOM.bip49panel = $("#bip49");
DOM.bip32path = $("#bip32-path");
DOM.bip44path = $("#bip44-path");
DOM.bip44purpose = $("#bip44 .purpose");
DOM.bip44accountXprv = $("#bip44 .account-xprv");
DOM.bip44accountXpub = $("#bip44 .account-xpub");
DOM.bip44change = $("#bip44 .change");
+ DOM.bip49unavailable = $("#bip49 .unavailable");
+ DOM.bip49available = $("#bip49 .available");
+ DOM.bip49path = $("#bip49-path");
+ DOM.bip49purpose = $("#bip49 .purpose");
+ DOM.bip49coin = $("#bip49 .coin");
+ DOM.bip49account = $("#bip49 .account");
+ DOM.bip49accountXprv = $("#bip49 .account-xprv");
+ DOM.bip49accountXpub = $("#bip49 .account-xpub");
+ DOM.bip49change = $("#bip49 .change");
DOM.generatedStrength = $(".generate-container .strength");
DOM.hardenedAddresses = $(".hardened-addresses");
DOM.addresses = $(".addresses");
DOM.bip32path.on("input", calcForDerivationPath);
DOM.bip44account.on("input", calcForDerivationPath);
DOM.bip44change.on("input", calcForDerivationPath);
+ DOM.bip49account.on("input", calcForDerivationPath);
+ DOM.bip49change.on("input", calcForDerivationPath);
DOM.tab.on("shown.bs.tab", calcForDerivationPath);
DOM.hardenedAddresses.on("change", calcForDerivationPath);
DOM.indexToggle.on("click", toggleIndexes);
// Event handlers
function networkChanged(e) {
+ clearDerivedKeys();
+ clearAddressesList();
var networkIndex = e.target.value;
- networks[networkIndex].onSelect();
+ var network = networks[networkIndex];
+ network.onSelect();
+ if (network.bip49available) {
+ showBip49();
+ }
+ else {
+ hideBip49();
+ }
if (seed != null) {
phraseChanged();
}
function calcForDerivationPath() {
showPending();
+ clearDerivedKeys();
clearAddressesList();
hideValidationError();
+ // Don't show bip49 if it's selected but network doesn't support it
+ if (bip49TabSelected() && !networkHasBip49()) {
+ return;
+ }
// Get the derivation path
var derivationPath = getDerivationPath();
var errorText = findDerivationPathErrors(derivationPath);
if (bip44TabSelected()) {
displayBip44Info();
}
+ if (bip49TabSelected()) {
+ displayBip49Info();
+ }
displayBip32Info();
hidePending();
}
console.log("Using derivation path from BIP44 tab: " + derivationPath);
return derivationPath;
}
+ if (bip49TabSelected()) {
+ var purpose = parseIntNoNaN(DOM.bip49purpose.val(), 49);
+ var coin = parseIntNoNaN(DOM.bip49coin.val(), 0);
+ var account = parseIntNoNaN(DOM.bip49account.val(), 0);
+ var change = parseIntNoNaN(DOM.bip49change.val(), 0);
+ var path = "m/";
+ path += purpose + "'/";
+ path += coin + "'/";
+ path += account + "'/";
+ path += change;
+ DOM.bip49path.val(path);
+ var derivationPath = DOM.bip49path.val();
+ console.log("Using derivation path from BIP49 tab: " + derivationPath);
+ return derivationPath;
+ }
else if (bip32TabSelected()) {
var derivationPath = DOM.bip32path.val();
console.log("Using derivation path from BIP32 tab: " + derivationPath);
DOM.bip44accountXpub.val(accountXpub);
}
+ function displayBip49Info() {
+ // Get the derivation path for the account
+ var purpose = parseIntNoNaN(DOM.bip49purpose.val(), 49);
+ var coin = parseIntNoNaN(DOM.bip49coin.val(), 0);
+ var account = parseIntNoNaN(DOM.bip49account.val(), 0);
+ var path = "m/";
+ path += purpose + "'/";
+ path += coin + "'/";
+ path += account + "'/";
+ // Calculate the account extended keys
+ var accountExtendedKey = calcBip32ExtendedKey(path);
+ var accountXprv = accountExtendedKey.toBase58();
+ var accountXpub = accountExtendedKey.neutered().toBase58();
+ // Display the extended keys
+ DOM.bip49accountXprv.val(accountXprv);
+ DOM.bip49accountXpub.val(accountXpub);
+ }
+
function displayBip32Info() {
// Display the key
DOM.seed.val(seed);
var self = this;
this.shouldGenerate = true;
var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
+ var isBip49 = bip49TabSelected();
+ var bip49available = networkHasBip49();
function init() {
calculateValues();
privkey = convertRipplePriv(privkey);
address = convertRippleAdrr(address);
}
+ // BIP49 addresses are different
+ if (isBip49) {
+ if (!bip49available) {
+ return;
+ }
+ var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer());
+ var scriptsig = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
+ var addressbytes = bitcoinjs.bitcoin.crypto.hash160(scriptsig);
+ var scriptpubkey = bitcoinjs.bitcoin.script.scriptHash.output.encode(addressbytes);
+ address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network)
+ }
addAddressToList(indexText, address, pubkey, privkey);
}, 50)
}
return DOM.bip32tab.hasClass("active");
}
+ function networkHasBip49() {
+ return networks[DOM.network.val()].bip49available;
+ }
+
+ function bip49TabSelected() {
+ return DOM.bip49tab.hasClass("active");
+ }
+
+ function setHdCoin(coinValue) {
+ DOM.bip44coin.val(coinValue);
+ DOM.bip49coin.val(coinValue);
+ }
+
+ function showBip49() {
+ DOM.bip49unavailable.addClass("hidden");
+ DOM.bip49available.removeClass("hidden");
+ }
+
+ function hideBip49() {
+ DOM.bip49available.addClass("hidden");
+ DOM.bip49unavailable.removeClass("hidden");
+ }
+
var networks = [
{
name: "BTC - Bitcoin",
+ bip49available: true,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.bitcoin;
- DOM.bip44coin.val(0);
+ setHdCoin(0);
},
},
{
name: "BTC - Bitcoin Testnet",
+ bip49available: true,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.testnet;
- DOM.bip44coin.val(1);
+ setHdCoin(1);
},
},
{
name: "CLAM - Clams",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.clam;
- DOM.bip44coin.val(23);
+ setHdCoin(23);
},
},
{
name: "CRW - Crown",
+ bip49available: false,
onSelect: function() {
- network = bitcoin.networks.crown;
- DOM.bip44coin.val(72);
+ network = bitcoinjs.bitcoin.networks.crown;
+ setHdCoin(72);
},
},
{
name: "DASH - Dash",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.dash;
- DOM.bip44coin.val(5);
+ setHdCoin(5);
},
},
{
name: "DASH - Dash Testnet",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.dashtn;
- DOM.bip44coin.val(1);
+ setHdCoin(1);
},
},
{
name: "DOGE - Dogecoin",
+ bip49available: false,
onSelect: function() {
- network = bitcoin.networks.dogecoin;
- DOM.bip44coin.val(3);
+ network = bitcoinjs.bitcoin.networks.dogecoin;
+ setHdCoin(3);
},
},
{
name: "ETH - Ethereum",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.bitcoin;
- DOM.bip44coin.val(60);
+ setHdCoin(60);
},
},
{
name: "GAME - GameCredits",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.game;
- DOM.bip44coin.val(101);
+ setHdCoin(101);
},
},
{
name: "JBS - Jumbucks",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.jumbucks;
- DOM.bip44coin.val(26);
+ setHdCoin(26);
},
},
{
name: "LTC - Litecoin",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.litecoin;
- DOM.bip44coin.val(2);
+ setHdCoin(2);
},
},
{
name: "NMC - Namecoin",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.namecoin;
- DOM.bip44coin.val(7);
+ setHdCoin(7);
},
},
{
name: "PPC - Peercoin",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.peercoin;
- DOM.bip44coin.val(6);
+ setHdCoin(6);
},
},
{
name: "SDC - ShadowCash",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.shadow;
- DOM.bip44coin.val(35);
+ setHdCoin(35);
},
},
{
name: "SDC - ShadowCash Testnet",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.shadowtn;
- DOM.bip44coin.val(1);
+ setHdCoin(1);
},
},
{
name: "SLM - Slimcoin",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.slimcoin;
- DOM.bip44coin.val(63);
+ setHdCoin(63);
},
},
{
name: "SLM - Slimcoin Testnet",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.slimcointn;
- DOM.bip44coin.val(111);
+ setHdCoin(111);
},
},
{
name: "VIA - Viacoin",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.viacoin;
- DOM.bip44coin.val(14);
+ setHdCoin(14);
},
},
{
name: "VIA - Viacoin Testnet",
+ bip49available: false,
onSelect: function() {
network = bitcoinjs.bitcoin.networks.viacointestnet;
- DOM.bip44coin.val(1);
+ setHdCoin(1);
},
},
{
name: "XRP - Ripple",
+ bip49available: false,
onSelect: function() {
- network = bitcoin.networks.bitcoin;
- DOM.bip44coin.val(144);
+ network = bitcoinjs.bitcoin.networks.bitcoin;
+ setHdCoin(144);
},
}
]
});
},
+// BIP49 official test vectors
+// https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki#test-vectors
+function() {
+page.open(url, function(status) {
+ // set the phrase and select bitcoin testnet
+ var expected = "2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
+ $(".network option[selected]").removeAttr("selected");
+ $(".network option").filter(function() {
+ return $(this).html() == "BTC - Bitcoin Testnet";
+ }).prop("selected", true);
+ $(".network").trigger("change");
+ $(".phrase").trigger("input");
+ });
+ // check the first address
+ waitForGenerate(function() {
+ var actual = page.evaluate(function() {
+ return $(".address:first").text();
+ });
+ if (actual != expected) {
+ console.log("BIP49 address is incorrect");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+});
+},
+
+// BIP49 derivation path is shown
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "m/49'/0'/0'/0";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability").trigger("input");
+ });
+ // check the derivation path of the first address
+ waitForGenerate(function() {
+ var actual = page.evaluate(function() {
+ return $("#bip49 .path").val();
+ });
+ if (actual != expected) {
+ console.log("BIP49 derivation path is incorrect");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+});
+},
+
+// BIP49 extended private key is shown
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "xprvA1hukYsW7QfX9CVsaDAKde4eryajKa4DKWb6m9YjSnqkiZHrahFwwTJfEQTwBQ5kptWT5pZMkkusT1oK8dc1efQ8VFfq4SLSPAWd7Cpt423";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability").trigger("input");
+ });
+ // check the BIP49 extended private key
+ waitForGenerate(function() {
+ var actual = page.evaluate(function() {
+ return $(".extended-priv-key").val();
+ });
+ if (actual != expected) {
+ console.log("BIP49 extended private key is incorrect");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+});
+},
+
+// BIP49 extended public key is shown
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "xpub6EhGA4QPwnDpMgaLgEhKzn1PR1RDj2n4gjWhZXxM18NjbMd18EaCVFd95gkLARJaBD2rXAYJED2gdkUbGn1KkrSzCKR554AdABUELoainnt";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability").trigger("input");
+ });
+ // check the BIP49 extended public key
+ waitForGenerate(function() {
+ var actual = page.evaluate(function() {
+ return $(".extended-pub-key").val();
+ });
+ if (actual != expected) {
+ console.log("BIP49 extended public key is incorrect");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+});
+},
+
+// BIP49 account field changes address list
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "381wg1GGN4rP88rNC9v7QWsiww63yLVPsn";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability").trigger("input");
+ });
+ waitForGenerate(function() {
+ // change the bip49 account field to 1
+ page.evaluate(function() {
+ $("#bip49 .account").val("1");
+ $("#bip49 .account").trigger("input");
+ });
+ waitForGenerate(function() {
+ // check the address for the new derivation path
+ var actual = page.evaluate(function() {
+ return $(".address:first").text();
+ });
+ if (actual != expected) {
+ console.log("BIP49 account field generates incorrect address");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+ });
+});
+},
+
+// BIP49 change field changes address list
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "3PEM7MiKed5konBoN66PQhK8r3hjGhy9dT";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability").trigger("input");
+ });
+ waitForGenerate(function() {
+ // change the bip49 change field to 1
+ page.evaluate(function() {
+ $("#bip49 .change").val("1");
+ $("#bip49 .change").trigger("input");
+ });
+ waitForGenerate(function() {
+ // check the address for the new derivation path
+ var actual = page.evaluate(function() {
+ return $(".address:first").text();
+ });
+ if (actual != expected) {
+ console.log("BIP49 change field generates incorrect address");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+ });
+});
+},
+
+// BIP49 account extendend private key is shown
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "xprv9y3uhgQbfQZbj3o98nfgLDwGGuCJjUn7GKArSAZXjKgMjSdYHjQmTyf78s22g6jsGrxXvHB6HJeFyvFSPkuYZajeTGMZVXV6aNLWw2fagCn";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability");
+ $(".phrase").trigger("input");
+ });
+ // check the BIP49 account extended private key
+ waitForGenerate(function() {
+ var actual = page.evaluate(function() {
+ return $("#bip49 .account-xprv").val();
+ });
+ if (actual != expected) {
+ console.log("BIP49 account extended private key is incorrect");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+});
+},
+
+// BIP49 account extendend public key is shown
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ var expected = "xpub6C3G7BwVVn7twXscEpCghMszpw2o8wVxdY6TEYy9HfDLcExgqGj21myazAiq6HSmW2F1cBiFqJa3D1cqcDpSh8pbZF5x4iqpd4PyJvd3gjB";
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability");
+ $(".phrase").trigger("input");
+ });
+ // check the BIP49 account extended public key
+ waitForGenerate(function() {
+ var actual = page.evaluate(function() {
+ return $("#bip49 .account-xpub").val();
+ });
+ if (actual != expected) {
+ console.log("BIP49 account extended public key is incorrect");
+ console.log("Expected: " + expected);
+ console.log("Actual: " + actual);
+ fail();
+ }
+ next();
+ });
+});
+},
+
+// Test selecting coin where bip49 is unavailable (eg CLAM)
+function() {
+page.open(url, function(status) {
+ // set the phrase
+ page.evaluate(function() {
+ $("#bip49-tab a").click();
+ $(".phrase").val("abandon abandon ability");
+ $(".phrase").trigger("input");
+ });
+ waitForGenerate(function() {
+ // select non-bip49 network, ie CLAM network
+ page.evaluate(function() {
+ $(".network option[selected]").removeAttr("selected");
+ $(".network option").filter(function() {
+ return $(this).html() == "CLAM - Clams";
+ }).prop("selected", true);
+ $(".network").trigger("change");
+ });
+ // check the BIP49 error is shown
+ var bip49ErrorShown = page.evaluate(function() {
+ var bip49hidden = $("#bip49 .available").hasClass("hidden");
+ bip49hidden = bip49hidden && !($("#bip49 .unavailable").hasClass("hidden"));
+ return bip49hidden;
+ });
+ if (!bip49ErrorShown) {
+ console.log("BIP49 error not shown for non-bip49 network");
+ fail();
+ }
+ // check there are no addresses shown
+ var addressCount = page.evaluate(function() {
+ return $(".address").length;
+ });
+ if (addressCount != 0) {
+ console.log("BIP49 address count for non-bip49 network is " + addressCount);
+ fail();
+ }
+ // check the derived keys are blank
+ var areBlank = page.evaluate(function() {
+ var prvKeyIsBlank = $(".extended-priv-key").val().length == 0;
+ var pubKeyIsBlank = $(".extended-pub-key").val().length == 0;
+ return prvKeyIsBlank && pubKeyIsBlank;
+ });
+ if (!areBlank) {
+ console.log("BIP49 extended keys for non-bip49 network are not blank ");
+ fail();
+ }
+ next();
+ });
+});
+},
+
// If you wish to add more tests, do so here...
// Here is a blank test template