<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>
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;
}
}
});
});
+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();
+ });
+ });
+});
+
});