diff --git a/lib/base_wizard.py b/lib/base_wizard.py index 4eafd39ec..1bb64fc62 100644 --- a/lib/base_wizard.py +++ b/lib/base_wizard.py @@ -232,14 +232,15 @@ class BaseWizard(object): self.derivation_dialog(f) def derivation_dialog(self, f): - default = bip44_derivation(0, False) + default = bip44_derivation(0, bip43_purpose=44) message = '\n'.join([ _('Enter your wallet derivation here.'), _('If you are not sure what this is, leave this field unchanged.') ]) presets = ( - ('legacy BIP44', bip44_derivation(0, False)), - ('p2sh-segwit BIP49', bip44_derivation(0, True)), + ('legacy BIP44', bip44_derivation(0, bip43_purpose=44)), + ('p2sh-segwit BIP49', bip44_derivation(0, bip43_purpose=49)), + ('native-segwit BIP84', bip44_derivation(0, bip43_purpose=84)), ) self.line_dialog(run_next=f, title=_('Derivation'), message=message, default=default, test=bitcoin.is_bip32_derivation, @@ -247,7 +248,10 @@ class BaseWizard(object): def on_hw_derivation(self, name, device_info, derivation): from .keystore import hardware_keystore - xtype = 'p2wpkh-p2sh' if derivation.startswith("m/49'/") else 'standard' + xtype = keystore.xtype_from_derivation(derivation) + if xtype not in ('standard', 'p2wpkh-p2sh'): + self.show_error(_('Hardware wallet support for this script type is not yet enabled.')) + return try: xpub = self.plugin.get_xpub(device_info.device.id_, derivation, xtype, self) except BaseException as e: diff --git a/lib/keystore.py b/lib/keystore.py index cfae5218c..40a747db0 100644 --- a/lib/keystore.py +++ b/lib/keystore.py @@ -571,10 +571,21 @@ def bip39_is_checksum_valid(mnemonic): def from_bip39_seed(seed, passphrase, derivation): k = BIP32_KeyStore({}) bip32_seed = bip39_to_seed(seed, passphrase) - t = 'p2wpkh-p2sh' if derivation.startswith("m/49'") else 'standard' # bip43 - k.add_xprv_from_seed(bip32_seed, t, derivation) + xtype = xtype_from_derivation(derivation) + k.add_xprv_from_seed(bip32_seed, xtype, derivation) return k + +def xtype_from_derivation(derivation): + """Returns the script type to be used for this derivation.""" + if derivation.startswith("m/84'"): + return 'p2wpkh' + elif derivation.startswith("m/49'"): + return 'p2wpkh-p2sh' + else: + return 'standard' + + # extended pubkeys def is_xpubkey(x_pubkey): @@ -671,10 +682,9 @@ is_private_key = lambda x: is_xprv(x) or is_private_key_list(x) is_bip32_key = lambda x: is_xprv(x) or is_xpub(x) -def bip44_derivation(account_id, segwit=False): - bip = 49 if segwit else 44 +def bip44_derivation(account_id, bip43_purpose=44): coin = 1 if bitcoin.NetworkConstants.TESTNET else 0 - return "m/%d'/%d'/%d'" % (bip, coin, int(account_id)) + return "m/%d'/%d'/%d'" % (bip43_purpose, coin, int(account_id)) def from_seed(seed, passphrase, is_p2sh): t = seed_type(seed) diff --git a/lib/tests/test_wallet_vertical.py b/lib/tests/test_wallet_vertical.py index f909db6d9..4a95cb119 100644 --- a/lib/tests/test_wallet_vertical.py +++ b/lib/tests/test_wallet_vertical.py @@ -175,6 +175,25 @@ class TestWalletKeystoreAddressIntegrity(unittest.TestCase): self.assertEqual(w.get_receiving_addresses()[0], '35ohQTdNykjkF1Mn9nAVEFjupyAtsPAK1W') self.assertEqual(w.get_change_addresses()[0], '3KaBTcviBLEJajTEMstsA2GWjYoPzPK7Y7') + @mock.patch.object(storage.WalletStorage, '_write') + def test_bip39_seed_bip84_native_segwit(self, mock_write): + # test case from bip84 + seed_words = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about' + self.assertEqual(keystore.bip39_is_checksum_valid(seed_words), (True, True)) + + ks = keystore.from_bip39_seed(seed_words, '', "m/84'/0'/0'") + + self.assertTrue(isinstance(ks, keystore.BIP32_KeyStore)) + + self.assertEqual(ks.xprv, 'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE') + self.assertEqual(ks.xpub, 'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs') + + w = self._create_standard_wallet(ks) + self.assertEqual(w.txin_type, 'p2wpkh') + + self.assertEqual(w.get_receiving_addresses()[0], 'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu') + self.assertEqual(w.get_change_addresses()[0], 'bc1q8c6fshw2dlwun7ekn9qwf37cu2rn755upcp6el') + @mock.patch.object(storage.WalletStorage, '_write') def test_electrum_multisig_seed_standard(self, mock_write): seed_words = 'blast uniform dragon fiscal ensure vast young utility dinosaur abandon rookie sure'