Skip to content

Commit

Permalink
Allow specifying BIP44 account when importing from USB (#360)
Browse files Browse the repository at this point in the history
  • Loading branch information
ben-kaufman committed Sep 7, 2020
1 parent 7cbc95d commit ad816d2
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 24 deletions.
54 changes: 35 additions & 19 deletions src/cryptoadvance/specter/hwi_rpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,13 +140,13 @@ def send_pin(self, pin='', device_type=None, path=None, passphrase='', chain='')
raise Exception("Invalid HWI device type %s, send_pin is only supported for Trezor and Keepkey devices" % device_type)

@locked(hwilock)
def extract_xpubs(self, device_type=None, path=None, fingerprint=None, passphrase='', chain=''):
def extract_xpubs(self, account=0, device_type=None, path=None, fingerprint=None, passphrase='', chain=''):
with self._get_client(device_type=device_type,
fingerprint=fingerprint,
path=path,
passphrase=passphrase,
chain=chain) as client:
xpubs = self._extract_xpubs_from_client(client)
xpubs = self._extract_xpubs_from_client(client, account)
return xpubs

@locked(hwilock)
Expand Down Expand Up @@ -230,7 +230,7 @@ def _get_client(self, device_type=None, path=None, fingerprint=None, passphrase=
else:
raise Exception('The device could not be found. Please check it is properly connected and try again')

def _extract_xpubs_from_client(self, client):
def _extract_xpubs_from_client(self, client, account=0):
try:
xpubs = ""
# Client will be configured for testnet if our Specter instance is
Expand All @@ -246,47 +246,63 @@ def _extract_xpubs_from_client(self, client):
# https://github.com/satoshilabs/slips/blob/master/slip-0132.md

# Extract nested Segwit
xpub = client.get_pubkey_at_path('m/49h/0h/0h')['xpub']
xpub = client.get_pubkey_at_path(
'm/49h/0h/{}h'.format(account)
)['xpub']
ypub = convert_xpub_prefix(xpub, b'\x04\x9d\x7c\xb2')
xpubs += "[%s/49'/0'/0']%s\n" % (master_fpr, ypub)
xpubs += "[{}/49'/0'/{}']{}\n".format(master_fpr, account, ypub)

# native Segwit
xpub = client.get_pubkey_at_path('m/84h/0h/0h')['xpub']
xpub = client.get_pubkey_at_path(
'm/84h/0h/{}h'.format(account)
)['xpub']
zpub = convert_xpub_prefix(xpub, b'\x04\xb2\x47\x46')
xpubs += "[%s/84'/0'/0']%s\n" % (master_fpr, zpub)
xpubs += "[{}/84'/0'/{}']{}\n".format(master_fpr, account, zpub)

# Multisig nested Segwit
xpub = client.get_pubkey_at_path('m/48h/0h/0h/1h')['xpub']
xpub = client.get_pubkey_at_path(
'm/48h/0h/{}h/1h'.format(account)
)['xpub']
Ypub = convert_xpub_prefix(xpub, b'\x02\x95\xb4\x3f')
xpubs += "[%s/48'/0'/0'/1']%s\n" % (master_fpr, Ypub)
xpubs += "[{}/48'/0'/{}'/1']{}\n".format(master_fpr, account, Ypub)

# Multisig native Segwit
xpub = client.get_pubkey_at_path('m/48h/0h/0h/2h')['xpub']
xpub = client.get_pubkey_at_path(
'm/48h/0h/{}h/2h'.format(account)
)['xpub']
Zpub = convert_xpub_prefix(xpub, b'\x02\xaa\x7e\xd3')
xpubs += "[%s/48'/0'/0'/2']%s\n" % (master_fpr, Zpub)
xpubs += "[{}/48'/0'/{}'/2']{}\n".format(master_fpr, account, Zpub)

# And testnet
client.is_testnet = True

# Testnet nested Segwit
xpub = client.get_pubkey_at_path('m/49h/1h/0h')['xpub']
xpub = client.get_pubkey_at_path(
'm/49h/1h/{}h'.format(account)
)['xpub']
upub = convert_xpub_prefix(xpub, b'\x04\x4a\x52\x62')
xpubs += "[%s/49'/1'/0']%s\n" % (master_fpr, upub)
xpubs += "[{}/49'/1'/{}']{}\n".format(master_fpr, account, upub)

# Testnet native Segwit
xpub = client.get_pubkey_at_path('m/84h/1h/0h')['xpub']
xpub = client.get_pubkey_at_path(
'm/84h/1h/{}h'.format(account)
)['xpub']
vpub = convert_xpub_prefix(xpub, b'\x04\x5f\x1c\xf6')
xpubs += "[%s/84'/1'/0']%s\n" % (master_fpr, vpub)
xpubs += "[{}/84'/1'/{}']{}\n".format(master_fpr, account, vpub)

# Testnet multisig nested Segwit
xpub = client.get_pubkey_at_path('m/48h/1h/0h/1h')['xpub']
xpub = client.get_pubkey_at_path(
'm/48h/1h/{}h/1h'.format(account)
)['xpub']
Upub = convert_xpub_prefix(xpub, b'\x02\x42\x89\xef')
xpubs += "[%s/48'/1'/0'/1']%s\n" % (master_fpr, Upub)
xpubs += "[{}/48'/1'/{}'/1']{}\n".format(master_fpr, account, Upub)

# Testnet multisig native Segwit
xpub = client.get_pubkey_at_path('m/48h/1h/0h/2h')['xpub']
xpub = client.get_pubkey_at_path(
'm/48h/1h/{}h/2h'.format(account)
)['xpub']
Vpub = convert_xpub_prefix(xpub, b'\x02\x57\x54\x83')
xpubs += "[%s/48'/1'/0'/2']%s\n" % (master_fpr, Vpub)
xpubs += "[{}/48'/1'/{}'/2']{}\n".format(master_fpr, account, Vpub)

# Do proper cleanup otherwise have to reconnect device to access again
client.close()
Expand Down
3 changes: 2 additions & 1 deletion src/cryptoadvance/specter/static/hwi.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,13 @@ class HWIBridge {
});
}

async getXpubs(device, passphrase=""){
async getXpubs(device, account=0, passphrase=""){
if(!('passphrase' in device)){
device.passphrase = passphrase;
}
return await this.fetch('extract_xpubs', {
device_type: device.type,
account: account,
path: device.path,
passphrase: device.passphrase,
});
Expand Down
17 changes: 13 additions & 4 deletions src/cryptoadvance/specter/templates/device/new_device.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,14 @@
<img src="/static/img/usb_tiny.svg" height="12px">Get over USB
</button>
</div>
<br>
<p class="note center" style="text-decoration: underline; cursor: pointer;" onclick="showPageOverlay('usb_import_account_number')">Specify account number (advanced)</p>
<div class="hidden" id="usb_import_account_number">
<h1>Select the acount you wish to import</h1>
<p class="center" style="max-width: 500px; margin: 5px auto;">You can set here which account number you wish to import.<br>Account derivation uses the number choosen here to derive an xpub as specified in <a style="color: #fff" href="https://github.com/bitcoin/bips/blob/master/bip-0044.mediawiki#account" target="_blank">BIP44</a>.</p>
Account # <input type="number" step="1" min="0" value="0" id="usb_account_number" style="max-width: 100px; min-width: 100px;"><br><br>
<p class="note center">This is an advanced feature.<br>If you're not familiar with account derivations please leave this as 0.</p><br>
<button class="btn centered" type="button" onclick="hidePageOverlay()">Close</button>
</div>
<div class="note">
Examples:<br>
<pre style="font-size: 1em">
Expand Down Expand Up @@ -222,13 +229,13 @@ upub5En4f7k8gaG2KDHvBeEYox...rFpJRHpiZ4DE
<script type="text/javascript">
{% include "device/components/add_keys.js" %}
async function getXpubs(device, passphrase=""){
async function getXpubs(device, account=0, passphrase=""){
// detect device
showHWIProgress("Extracting public keys...",
`It may take a while.<br><br>Keep your ${capitalize(device.type)} connected.`);
let result = [];
try {
result = await hwi.getXpubs(device, passphrase);
result = await hwi.getXpubs(device, account, passphrase);
} catch (error) {
handleHWIError(error);
return null;
Expand Down Expand Up @@ -261,7 +268,9 @@ upub5En4f7k8gaG2KDHvBeEYox...rFpJRHpiZ4DE
if(device == null){
return;
}
let xpubs = await getXpubs(device);
let account = document.getElementById('usb_account_number').value;
let xpubs = await getXpubs(device, account);
if(xpubs == null){
return;
}
Expand Down

0 comments on commit ad816d2

Please sign in to comment.