From c49e881294343b762109bb8104e7c1b45898c894 Mon Sep 17 00:00:00 2001 From: Ian Coleman Date: Wed, 22 Nov 2017 12:11:48 +1100 Subject: [PATCH] Add BIP141 tab for full segwit compatibility --- src/index.html | 41 ++++++++++++++++ src/js/bitcoinjs-extensions.js | 33 ------------- src/js/index.js | 88 +++++++++++++++++++++++----------- src/js/segwit-parameters.js | 58 ++++++++++++++++++++++ tests/spec/tests.js | 47 ++++++++++++++++++ 5 files changed, 205 insertions(+), 62 deletions(-) create mode 100644 src/js/segwit-parameters.js diff --git a/src/index.html b/src/index.html index b638b26..0126bef 100644 --- a/src/index.html +++ b/src/index.html @@ -301,6 +301,9 @@
  • BIP49
  • +
  • + BIP141 +
  • @@ -544,6 +547,43 @@
    +
    +
    +
    + +
    +
    +
    +

    + For more info see the + BIP141 spec +

    +
    +
    + +
    + +
    +
    +
    + +
    + +
    +
    +
    +
    +
    @@ -830,6 +870,7 @@ + diff --git a/src/js/bitcoinjs-extensions.js b/src/js/bitcoinjs-extensions.js index 63f7c6e..1509a6c 100644 --- a/src/js/bitcoinjs-extensions.js +++ b/src/js/bitcoinjs-extensions.js @@ -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: { diff --git a/src/js/index.js b/src/js/index.js index b88e9d7..261a6d1 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -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"); @@ -72,6 +73,10 @@ 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"); @@ -111,6 +116,8 @@ 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); @@ -138,6 +145,7 @@ var network = networks[networkIndex]; network.onSelect(); if (network.segwitAvailable) { + adjustNetworkForSegwit(); showSegwitAvailable(); } else { @@ -343,7 +351,7 @@ if (bip44TabSelected()) { displayBip44Info(); } - if (bip49TabSelected()) { + else if (bip49TabSelected()) { displayBip49Info(); } displayBip32Info(); @@ -523,7 +531,7 @@ 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); @@ -543,6 +551,11 @@ 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"); } @@ -673,7 +686,16 @@ } 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) { @@ -683,6 +705,8 @@ var useHardenedAddresses = DOM.hardenedAddresses.prop("checked"); var isSegwit = segwitSelected(); var segwitAvailable = networkHasSegwit(); + var isP2wpkh = p2wpkhSelected(); + var isP2wpkhInP2sh = p2wpkhInP2shSelected(); function init() { calculateValues(); @@ -731,11 +755,18 @@ 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) { @@ -1233,6 +1264,10 @@ return DOM.bip49tab.hasClass("active"); } + function bip141TabSelected() { + return DOM.bip141tab.hasClass("active"); + } + function setHdCoin(coinValue) { DOM.bip44coin.val(coinValue); DOM.bip49coin.val(coinValue); @@ -1241,11 +1276,15 @@ 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() { @@ -1266,27 +1305,18 @@ // 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 index 0000000..38cf9e0 --- /dev/null +++ b/src/js/segwit-parameters.js @@ -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 +}; + +})(); diff --git a/tests/spec/tests.js b/tests/spec/tests.js index f3ffbbf..2b28d73 100644 --- a/tests/spec/tests.js +++ b/tests/spec/tests.js @@ -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(); + }); + }); +}); + }); -- 2.41.0