Skip to content

Commit

Permalink
Add BIP141 tab for full segwit compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
iancoleman committed Nov 22, 2017
1 parent 0fd67b5 commit c49e881
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 62 deletions.
41 changes: 41 additions & 0 deletions src/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ <h2>Derivation Path</h2>
<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">
Expand Down Expand Up @@ -544,6 +547,43 @@ <h2>Derivation Path</h2>
</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">
Expand Down Expand Up @@ -830,6 +870,7 @@ <h3>Libraries</h3>
<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>
Expand Down
33 changes: 0 additions & 33 deletions src/js/bitcoinjs-extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
88 changes: 59 additions & 29 deletions src/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -138,6 +145,7 @@
var network = networks[networkIndex];
network.onSelect();
if (network.segwitAvailable) {
adjustNetworkForSegwit();
showSegwitAvailable();
}
else {
Expand Down Expand Up @@ -343,7 +351,7 @@
if (bip44TabSelected()) {
displayBip44Info();
}
if (bip49TabSelected()) {
else if (bip49TabSelected()) {
displayBip49Info();
}
displayBip32Info();
Expand Down Expand Up @@ -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);
Expand All @@ -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");
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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();
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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);
Expand All @@ -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() {
Expand All @@ -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;
}
}

Expand Down
58 changes: 58 additions & 0 deletions src/js/segwit-parameters.js
Original file line number Diff line number Diff line change
@@ -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
};

})();
47 changes: 47 additions & 0 deletions tests/spec/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -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();
});
});
});

});

0 comments on commit c49e881

Please sign in to comment.