]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/commitdiff
Add BIP49 tab
authorIan Coleman <coleman.ian@gmail.com>
Wed, 2 Aug 2017 07:02:43 +0000 (17:02 +1000)
committerIan Coleman <coleman.ian@gmail.com>
Wed, 2 Aug 2017 07:28:39 +0000 (17:28 +1000)
src/index.html
src/js/bitcoinjs-extensions.js
src/js/index.js
tests.js

index 6a5e6447f8fdca8bd6d513577ad38125c6b49d76..31e8a2e2b23de5cd14113d1aa3cc3e9350aaf477 100644 (file)
                         <li id="bip44-tab" class="active">
                             <a href="#bip44" role="tab" data-toggle="tab">BIP44</a>
                         </li>
                         <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">
                     </ul>
                     <div class="derivation-type tab-content">
                         <div id="bip44" class="tab-pane active">
                                 </div>
                             </form>
                         </div>
                                 </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">
                     </div>
                     <form class="form-horizontal" role="form">
                         <div class="form-group">
index ac2123cbb95ed84432a8fa06b104f98d0932376d..1b40135191838c8b5829982f3dcd51083daa2651 100644 (file)
@@ -31,7 +31,7 @@ bitcoinjs.bitcoin.networks.clam = {
   wif: 0x85,
 };
 
   wif: 0x85,
 };
 
-bitcoin.networks.crown = {
+bitcoinjs.bitcoin.networks.crown = {
   messagePrefix: "unused",
   bip32: {
     public: 0x0488b21e,
   messagePrefix: "unused",
   bip32: {
     public: 0x0488b21e,
@@ -42,7 +42,7 @@ bitcoin.networks.crown = {
   wif: 0x80,
 };
 
   wif: 0x80,
 };
 
-bitcoin.networks.dash = {
+bitcoinjs.bitcoin.networks.dash = {
   messagePrefix: "unused",
   bip32: {
     public: 0x0488b21e,
   messagePrefix: "unused",
   bip32: {
     public: 0x0488b21e,
index 763967ca45bfb73a47eb7942d87bda6890091eda..012980876f5733f6898a0566203e37157a131ccf 100644 (file)
     DOM.extendedPubKey = $(".extended-pub-key");
     DOM.bip32tab = $("#bip32-tab");
     DOM.bip44tab = $("#bip44-tab");
     DOM.extendedPubKey = $(".extended-pub-key");
     DOM.bip32tab = $("#bip32-tab");
     DOM.bip44tab = $("#bip44-tab");
+    DOM.bip49tab = $("#bip49-tab");
     DOM.bip32panel = $("#bip32");
     DOM.bip44panel = $("#bip44");
     DOM.bip32panel = $("#bip32");
     DOM.bip44panel = $("#bip44");
+    DOM.bip49panel = $("#bip49");
     DOM.bip32path = $("#bip32-path");
     DOM.bip44path = $("#bip44-path");
     DOM.bip44purpose = $("#bip44 .purpose");
     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.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.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.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);
         DOM.tab.on("shown.bs.tab", calcForDerivationPath);
         DOM.hardenedAddresses.on("change", calcForDerivationPath);
         DOM.indexToggle.on("click", toggleIndexes);
     // Event handlers
 
     function networkChanged(e) {
     // Event handlers
 
     function networkChanged(e) {
+        clearDerivedKeys();
+        clearAddressesList();
         var networkIndex = e.target.value;
         var networkIndex = e.target.value;
-        networks[networkIndex].onSelect();
+        var network = networks[networkIndex];
+        network.onSelect();
+        if (network.bip49available) {
+            showBip49();
+        }
+        else {
+            hideBip49();
+        }
         if (seed != null) {
             phraseChanged();
         }
         if (seed != null) {
             phraseChanged();
         }
 
     function calcForDerivationPath() {
         showPending();
 
     function calcForDerivationPath() {
         showPending();
+        clearDerivedKeys();
         clearAddressesList();
         hideValidationError();
         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);
         // Get the derivation path
         var derivationPath = getDerivationPath();
         var errorText = findDerivationPathErrors(derivationPath);
         if (bip44TabSelected()) {
             displayBip44Info();
         }
         if (bip44TabSelected()) {
             displayBip44Info();
         }
+        if (bip49TabSelected()) {
+            displayBip49Info();
+        }
         displayBip32Info();
         hidePending();
     }
         displayBip32Info();
         hidePending();
     }
             console.log("Using derivation path from BIP44 tab: " + derivationPath);
             return derivationPath;
         }
             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);
         else if (bip32TabSelected()) {
             var derivationPath = DOM.bip32path.val();
             console.log("Using derivation path from BIP32 tab: " + derivationPath);
         DOM.bip44accountXpub.val(accountXpub);
     }
 
         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);
     function displayBip32Info() {
         // Display the key
         DOM.seed.val(seed);
         var self = this;
         this.shouldGenerate = true;
         var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
         var self = this;
         this.shouldGenerate = true;
         var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
+        var isBip49 = bip49TabSelected();
+        var bip49available = networkHasBip49();
 
         function init() {
             calculateValues();
 
         function init() {
             calculateValues();
                     privkey = convertRipplePriv(privkey);
                     address = convertRippleAdrr(address);
                 }
                     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)
         }
                 addAddressToList(indexText, address, pubkey, privkey);
             }, 50)
         }
         return DOM.bip32tab.hasClass("active");
     }
 
         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",
     var networks = [
         {
             name: "BTC - Bitcoin",
+            bip49available: true,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.bitcoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.bitcoin;
-                DOM.bip44coin.val(0);
+                setHdCoin(0);
             },
         },
         {
             name: "BTC - Bitcoin Testnet",
             },
         },
         {
             name: "BTC - Bitcoin Testnet",
+            bip49available: true,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.testnet;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.testnet;
-                DOM.bip44coin.val(1);
+                setHdCoin(1);
             },
         },
         {
             name: "CLAM - Clams",
             },
         },
         {
             name: "CLAM - Clams",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.clam;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.clam;
-                DOM.bip44coin.val(23);
+                setHdCoin(23);
             },
         },
         {
             name: "CRW - Crown",
             },
         },
         {
             name: "CRW - Crown",
+            bip49available: false,
             onSelect: function() {
             onSelect: function() {
-                network = bitcoin.networks.crown;
-                DOM.bip44coin.val(72);
+                network = bitcoinjs.bitcoin.networks.crown;
+                setHdCoin(72);
             },
         },
         {
             name: "DASH - Dash",
             },
         },
         {
             name: "DASH - Dash",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.dash;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.dash;
-                DOM.bip44coin.val(5);
+                setHdCoin(5);
             },
         },
         {
             name: "DASH - Dash Testnet",
             },
         },
         {
             name: "DASH - Dash Testnet",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.dashtn;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.dashtn;
-                DOM.bip44coin.val(1);
+                setHdCoin(1);
             },
         },
         {
             name: "DOGE - Dogecoin",
             },
         },
         {
             name: "DOGE - Dogecoin",
+            bip49available: false,
             onSelect: function() {
             onSelect: function() {
-                network = bitcoin.networks.dogecoin;
-                DOM.bip44coin.val(3);
+                network = bitcoinjs.bitcoin.networks.dogecoin;
+                setHdCoin(3);
             },
         },
         {
             name: "ETH - Ethereum",
             },
         },
         {
             name: "ETH - Ethereum",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.bitcoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.bitcoin;
-                DOM.bip44coin.val(60);
+                setHdCoin(60);
             },
         },
         {
             name: "GAME - GameCredits",
             },
         },
         {
             name: "GAME - GameCredits",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.game;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.game;
-                DOM.bip44coin.val(101);
+                setHdCoin(101);
             },
         },
         {
             name: "JBS - Jumbucks",
             },
         },
         {
             name: "JBS - Jumbucks",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.jumbucks;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.jumbucks;
-                DOM.bip44coin.val(26);
+                setHdCoin(26);
             },
         },
         {
             name: "LTC - Litecoin",
             },
         },
         {
             name: "LTC - Litecoin",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.litecoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.litecoin;
-                DOM.bip44coin.val(2);
+                setHdCoin(2);
             },
         },
         {
             name: "NMC - Namecoin",
             },
         },
         {
             name: "NMC - Namecoin",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.namecoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.namecoin;
-                DOM.bip44coin.val(7);
+                setHdCoin(7);
             },
         },
         {
             name: "PPC - Peercoin",
             },
         },
         {
             name: "PPC - Peercoin",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.peercoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.peercoin;
-                DOM.bip44coin.val(6);
+                setHdCoin(6);
             },
         },
         {
             name: "SDC - ShadowCash",
             },
         },
         {
             name: "SDC - ShadowCash",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.shadow;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.shadow;
-                DOM.bip44coin.val(35);
+                setHdCoin(35);
             },
         },
         {
             name: "SDC - ShadowCash Testnet",
             },
         },
         {
             name: "SDC - ShadowCash Testnet",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.shadowtn;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.shadowtn;
-                DOM.bip44coin.val(1);
+                setHdCoin(1);
             },
         },
         {
             name: "SLM - Slimcoin",
             },
         },
         {
             name: "SLM - Slimcoin",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.slimcoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.slimcoin;
-                DOM.bip44coin.val(63);
+                setHdCoin(63);
             },
         },
         {
             name: "SLM - Slimcoin Testnet",
             },
         },
         {
             name: "SLM - Slimcoin Testnet",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.slimcointn;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.slimcointn;
-                DOM.bip44coin.val(111);
+                setHdCoin(111);
             },
         },
         {
             name: "VIA - Viacoin",
             },
         },
         {
             name: "VIA - Viacoin",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.viacoin;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.viacoin;
-                DOM.bip44coin.val(14);
+                setHdCoin(14);
             },
         },
         {
             name: "VIA - Viacoin Testnet",
             },
         },
         {
             name: "VIA - Viacoin Testnet",
+            bip49available: false,
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.viacointestnet;
             onSelect: function() {
                 network = bitcoinjs.bitcoin.networks.viacointestnet;
-                DOM.bip44coin.val(1);
+                setHdCoin(1);
             },
         },
         {
             name: "XRP - Ripple",
             },
         },
         {
             name: "XRP - Ripple",
+            bip49available: false,
             onSelect: function() {
             onSelect: function() {
-                network = bitcoin.networks.bitcoin;
-                DOM.bip44coin.val(144);
+                network = bitcoinjs.bitcoin.networks.bitcoin;
+                setHdCoin(144);
             },
         }
     ]
             },
         }
     ]
index ea2904336ba6ee9ed12058a4156f5beb22bfb511..b6713b040e371803fab23cd47b5b555d7750d6db 100644 (file)
--- a/tests.js
+++ b/tests.js
@@ -3726,6 +3726,280 @@ page.open(url, function(status) {
 });
 },
 
 });
 },
 
+// 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
 // If you wish to add more tests, do so here...
 
 // Here is a blank test template