]> git.immae.eu Git - perso/Immae/Projets/Cryptomonnaies/BIP39.git/commitdiff
Add BIP141 tab for full segwit compatibility
authorIan Coleman <ian@iancoleman.io>
Wed, 22 Nov 2017 01:11:48 +0000 (12:11 +1100)
committerIan Coleman <ian@iancoleman.io>
Wed, 22 Nov 2017 02:03:47 +0000 (13:03 +1100)
src/index.html
src/js/bitcoinjs-extensions.js
src/js/index.js
src/js/segwit-parameters.js [new file with mode: 0644]
tests/spec/tests.js

index b638b2623d860c9808337e67dfa5fffa5121a0c8..0126bef19d80049e56fe8c0b9451ed3ce910a69b 100644 (file)
                         <li id="bip49-tab">
                             <a href="#bip49" role="tab" data-toggle="tab">BIP49</a>
                         </li>
+                        <li id="bip141-tab">
+                            <a href="#bip141" role="tab" data-toggle="tab">BIP141</a>
+                        </li>
                     </ul>
                     <div class="derivation-type tab-content">
                         <div id="bip44" class="tab-pane active">
                                 </div>
                             </form>
                         </div>
+                        <div id="bip141" 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>BIP141 is unavailable for this coin.</p>
+                                        </div>
+                                    </div>
+                                </div>
+                                <div class="available">
+                                    <div class="col-sm-2"></div>
+                                    <div class="col-sm-10">
+                                        <p>
+                                            For more info see the
+                                            <a href="https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki" target="_blank">BIP141 spec</a>
+                                        </p>
+                                    </div>
+                                    <div class="form-group">
+                                        <label for="bip141-path" class="col-sm-2 control-label">BIP32 Derivation Path</label>
+                                        <div class="col-sm-10">
+                                            <input id="bip141-path" type="text" class="bip141-path form-control" value="m/0">
+                                        </div>
+                                    </div>
+                                    <div class="form-group">
+                                        <label class="col-sm-2 control-label">Script Semantics</label>
+                                        <div class="col-sm-10">
+                                            <select class="form-control bip141-semantics">
+                                                <option value="p2wpkh">P2WPKH</option>
+                                                <option value="p2wpkh-p2sh" selected>P2WPKH nested in P2SH</option>
+                                            </select>
+                                        </div>
+                                    </div>
+                                </div>
+                            </form>
+                        </div>
                     </div>
                     <form class="form-horizontal" role="form">
                         <div class="form-group">
         <script src="js/jquery.qrcode.min.js"></script>
         <script src="js/bitcoinjs-3.3.0.js"></script>
         <script src="js/bitcoinjs-extensions.js"></script>
+        <script src="js/segwit-parameters.js"></script>
         <script src="js/ethereumjs-util.js"></script>
         <script src="js/ripple-util.js"></script>
         <script src="js/sjcl-bip39.js"></script>
index 63f7c6e0c94ee970cf7427bb556d757c2abde328..1509a6c4e70eac6817d7876b819811f0870a6857 100644 (file)
@@ -284,39 +284,6 @@ bitcoinjs.bitcoin.networks.monacoin = {
     wif: 0xb0
 };
 
-bitcoinjs.bitcoin.networks.bitcoinBip49 = {
-    messagePrefix: '\x18Bitcoin Signed Message:\n',
-    bip32: {
-      public: 0x049d7cb2,
-      private: 0x049d7878
-    },
-    pubKeyHash: 0x00,
-    scriptHash: 0x05,
-    wif: 0x80
-};
-
-bitcoinjs.bitcoin.networks.testnetBip49 = {
-    messagePrefix: '\x18Bitcoin Signed Message:\n',
-    bip32: {
-      public: 0x044a5262,
-      private: 0x044a4e28
-    },
-    pubKeyHash: 0x6f,
-    scriptHash: 0xc4,
-    wif: 0xef
-};
-
-bitcoinjs.bitcoin.networks.litecoinBip49 = {
-    messagePrefix: '\x19Litecoin Signed Message:\n',
-    bip32: {
-      public: 0x01b26ef6,
-      private: 0x01b26792
-    },
-    pubKeyHash: 0x30,
-    scriptHash: 0x32,
-    wif: 0xb0
-};
-
 bitcoinjs.bitcoin.networks.litecoinXprv = {
     messagePrefix: '\x19Litecoin Signed Message:\n',
     bip32: {
index b88e9d730704aa85114af5736b0c287771cb7a88..261a6d19c43005d77809bae797f7f1726fdbee0e 100644 (file)
@@ -52,6 +52,7 @@
     DOM.bip32tab = $("#bip32-tab");
     DOM.bip44tab = $("#bip44-tab");
     DOM.bip49tab = $("#bip49-tab");
+    DOM.bip141tab = $("#bip141-tab");
     DOM.bip32panel = $("#bip32");
     DOM.bip44panel = $("#bip44");
     DOM.bip49panel = $("#bip49");
     DOM.bip49accountXprv = $("#bip49 .account-xprv");
     DOM.bip49accountXpub = $("#bip49 .account-xpub");
     DOM.bip49change = $("#bip49 .change");
+    DOM.bip141unavailable = $("#bip141 .unavailable");
+    DOM.bip141available = $("#bip141 .available");
+    DOM.bip141path = $("#bip141-path");
+    DOM.bip141semantics = $(".bip141-semantics");
     DOM.generatedStrength = $(".generate-container .strength");
     DOM.hardenedAddresses = $(".hardened-addresses");
     DOM.useBitpayAddressesContainer = $(".use-bitpay-addresses-container");
         DOM.bip44change.on("input", calcForDerivationPath);
         DOM.bip49account.on("input", calcForDerivationPath);
         DOM.bip49change.on("input", calcForDerivationPath);
+        DOM.bip141path.on("input", calcForDerivationPath);
+        DOM.bip141semantics.on("change", tabChanged);
         DOM.tab.on("shown.bs.tab", tabChanged);
         DOM.hardenedAddresses.on("change", calcForDerivationPath);
         DOM.indexToggle.on("click", toggleIndexes);
         var network = networks[networkIndex];
         network.onSelect();
         if (network.segwitAvailable) {
+            adjustNetworkForSegwit();
             showSegwitAvailable();
         }
         else {
         if (bip44TabSelected()) {
             displayBip44Info();
         }
-        if (bip49TabSelected()) {
+        else if (bip49TabSelected()) {
             displayBip49Info();
         }
         displayBip32Info();
             console.log("Using derivation path from BIP44 tab: " + derivationPath);
             return derivationPath;
         }
-        if (bip49TabSelected()) {
+        else if (bip49TabSelected()) {
             var purpose = parseIntNoNaN(DOM.bip49purpose.val(), 49);
             var coin = parseIntNoNaN(DOM.bip49coin.val(), 0);
             var account = parseIntNoNaN(DOM.bip49account.val(), 0);
             console.log("Using derivation path from BIP32 tab: " + derivationPath);
             return derivationPath;
         }
+        else if (bip141TabSelected()) {
+            var derivationPath = DOM.bip141path.val();
+            console.log("Using derivation path from BIP141 tab: " + derivationPath);
+            return derivationPath;
+        }
         else {
             console.log("Unknown derivation path");
         }
     }
 
     function segwitSelected() {
-        return bip49TabSelected();
+        return bip49TabSelected() || bip141TabSelected();
+    }
+
+    function p2wpkhSelected() {
+        return bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh";
+    }
+
+    function p2wpkhInP2shSelected() {
+        return bip49TabSelected() ||
+            (bip141TabSelected() && DOM.bip141semantics.val() == "p2wpkh-p2sh");
     }
 
     function TableRow(index, isLast) {
         var useHardenedAddresses = DOM.hardenedAddresses.prop("checked");
         var isSegwit = segwitSelected();
         var segwitAvailable = networkHasSegwit();
+        var isP2wpkh = p2wpkhSelected();
+        var isP2wpkhInP2sh = p2wpkhInP2shSelected();
 
         function init() {
             calculateValues();
                     if (!segwitAvailable) {
                         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)
+                    if (isP2wpkh) {
+                        var keyhash = bitcoinjs.bitcoin.crypto.hash160(key.getPublicKeyBuffer());
+                        var scriptpubkey = bitcoinjs.bitcoin.script.witnessPubKeyHash.output.encode(keyhash);
+                        address = bitcoinjs.bitcoin.address.fromOutputScript(scriptpubkey, network)
+                    }
+                    else if (isP2wpkhInP2sh) {
+                        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);
                 if (isLast) {
         return DOM.bip49tab.hasClass("active");
     }
 
+    function bip141TabSelected() {
+        return DOM.bip141tab.hasClass("active");
+    }
+
     function setHdCoin(coinValue) {
         DOM.bip44coin.val(coinValue);
         DOM.bip49coin.val(coinValue);
     function showSegwitAvailable() {
         DOM.bip49unavailable.addClass("hidden");
         DOM.bip49available.removeClass("hidden");
+        DOM.bip141unavailable.addClass("hidden");
+        DOM.bip141available.removeClass("hidden");
     }
 
     function showSegwitUnavailable() {
         DOM.bip49available.addClass("hidden");
         DOM.bip49unavailable.removeClass("hidden");
+        DOM.bip141available.addClass("hidden");
+        DOM.bip141unavailable.removeClass("hidden");
     }
 
     function useBitpayAddresses() {
         // to avoid accidentally importing BIP49 xpub to BIP44 watch only
         // wallet.
         // See https://github.com/iancoleman/bip39/issues/125
-        if (segwitSelected()) {
-            if (network == bitcoinjs.bitcoin.networks.bitcoin) {
-                network = bitcoinjs.bitcoin.networks.bitcoinBip49;
-            }
-            else if (network == bitcoinjs.bitcoin.networks.testnet) {
-                network = bitcoinjs.bitcoin.networks.testnetBip49;
-            }
-            else if (network == bitcoinjs.bitcoin.networks.litecoin) {
-                network = bitcoinjs.bitcoin.networks.litecoinBip49;
-            }
-        }
-        else {
-            if (network == bitcoinjs.bitcoin.networks.bitcoinBip49) {
-                network = bitcoinjs.bitcoin.networks.bitcoin;
-            }
-            else if (network == bitcoinjs.bitcoin.networks.testnetBip49) {
-                network = bitcoinjs.bitcoin.networks.testnet;
-            }
-            else if (network == bitcoinjs.bitcoin.networks.litecoinBip49) {
-                network = bitcoinjs.bitcoin.networks.litecoin;
-            }
+        var segwitNetworks = null;
+        // if a segwit network is alread selected, need to use base network to
+        // look up new parameters
+        if ("baseNetwork" in network) {
+            network = bitcoinjs.bitcoin.networks[network.baseNetwork];
+        }
+        // choose the right segwit params
+        if (p2wpkhSelected() && "p2wpkh" in network) {
+            network = network.p2wpkh;
+        }
+        else if (p2wpkhInP2shSelected() && "p2wpkhInP2sh" in network) {
+            network = network.p2wpkhInP2sh;
         }
     }
 
diff --git a/src/js/segwit-parameters.js b/src/js/segwit-parameters.js
new file mode 100644 (file)
index 0000000..38cf9e0
--- /dev/null
@@ -0,0 +1,58 @@
+(function() {
+
+// p2wpkh
+
+bitcoinjs.bitcoin.networks.bitcoin.p2wpkh = {
+    baseNetwork: "bitcoin",
+    messagePrefix: '\x18Bitcoin Signed Message:\n',
+    bech32: 'bc',
+    bip32: {
+        public: 0x04b24746,
+        private: 0x04b2430c
+    },
+    pubKeyHash: 0x00,
+    scriptHash: 0x05,
+    wif: 0x80
+};
+
+// p2wpkh in p2sh
+
+bitcoinjs.bitcoin.networks.bitcoin.p2wpkhInP2sh = {
+    baseNetwork: "bitcoin",
+    messagePrefix: '\x18Bitcoin Signed Message:\n',
+    bech32: 'bc',
+    bip32: {
+        public: 0x049d7cb2,
+        private: 0x049d7878
+    },
+    pubKeyHash: 0x00,
+    scriptHash: 0x05,
+    wif: 0x80
+};
+
+bitcoinjs.bitcoin.networks.testnet.p2wpkhInP2sh = {
+    baseNetwork: "testnet",
+    messagePrefix: '\x18Bitcoin Signed Message:\n',
+    bech32: 'tb',
+    bip32: {
+        public: 0x044a5262,
+        private: 0x044a4e28
+    },
+    pubKeyHash: 0x6f,
+    scriptHash: 0xc4,
+    wif: 0xef
+};
+
+bitcoinjs.bitcoin.networks.litecoin.p2wpkhInP2sh = {
+    baseNetwork: "litecoin",
+    messagePrefix: '\x19Litecoin Signed Message:\n',
+    bip32: {
+        public: 0x01b26ef6,
+        private: 0x01b26792
+    },
+    pubKeyHash: 0x30,
+    scriptHash: 0x32,
+    wif: 0xb0
+};
+
+})();
index f3ffbbfcdb3d0bdd2e6385c80d59a7d2bc7c4739..2b28d739eb354beaa2e5b20ab3897097e9e10445 100644 (file)
@@ -2596,4 +2596,51 @@ it('Can generate more addresses from a custom index', function(done) {
     });
 });
 
+it('Can generate BIP141 addresses with P2WPKH-in-P2SH semanitcs', function(done) {
+    // Sourced from BIP49 official test specs
+    driver.findElement(By.css('#bip141-tab a'))
+        .click();
+    driver.findElement(By.css('.bip141-path'))
+        .clear();
+    driver.findElement(By.css('.bip141-path'))
+        .sendKeys("m/49'/1'/0'/0");
+    selectNetwork("BTC - Bitcoin Testnet");
+    driver.findElement(By.css(".phrase"))
+        .sendKeys("abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about");
+    driver.sleep(generateDelay).then(function() {
+        getFirstAddress(function(address) {
+            expect(address).toBe("2Mww8dCYPUpKHofjgcXcBCEGmniw9CoaiD2");
+            done();
+        });
+    });
+});
+
+it('Can generate BIP141 addresses with P2WPKH semanitcs', function(done) {
+    // This result tested against bitcoinjs-lib test spec for segwit address
+    // using the first private key of this mnemonic and default path m/0
+    // https://github.com/bitcoinjs/bitcoinjs-lib/blob/9c8503cab0c6c30a95127042703bc18e8d28c76d/test/integration/addresses.js#L50
+    // so whilst not directly comparable, substituting the private key produces
+    // identical results between this tool and the bitcoinjs-lib test.
+    // Private key generated is:
+    // L3L8Nu9whawPBNLGtFqDhKut9DKKfG3CQoysupT7BimqVCZsLFNP
+    driver.findElement(By.css('#bip141-tab a'))
+        .click();
+    // Choose P2WPKH
+    driver.executeScript(function() {
+        $(".bip141-semantics option[selected]").removeAttr("selected");
+        $(".bip141-semantics option").filter(function(i,e) {
+            return $(e).html() == "P2WPKH";
+        }).prop("selected", true);
+        $(".bip141-semantics").trigger("change");
+    });
+    driver.findElement(By.css(".phrase"))
+        .sendKeys("abandon abandon ability");
+    driver.sleep(generateDelay).then(function() {
+        getFirstAddress(function(address) {
+            expect(address).toBe("bc1qfwu6a5a3evygrk8zvdxxvz4547lmpyx5vsfxe9");
+            done();
+        });
+    });
+});
+
 });