From a2220dd12f62c07d59812a7d33e507c38060d645 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Thu, 13 Jul 2017 20:19:52 +0300 Subject: [PATCH] bip44 compliance for segwit wallets --- jmclient/jmclient/wallet.py | 34 +++++++++++++++++++++++++------ jmclient/jmclient/wallet_utils.py | 30 ++++++++++++++++----------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 5d09d61..8139cef 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -160,11 +160,7 @@ class Wallet(AbstractWallet): if extend_mixdepth and len(self.index_cache) > max_mix_depth: self.max_mix_depth = len(self.index_cache) self.gaplimit = gaplimit - master = btc.bip32_master_key(self.seed, (btc.MAINNET_PRIVATE if - get_network() == 'mainnet' else btc.TESTNET_PRIVATE)) - m_0 = btc.bip32_ckd(master, 0) - mixing_depth_keys = [btc.bip32_ckd(m_0, c) - for c in range(self.max_mix_depth)] + mixing_depth_keys = self.get_mixing_depth_keys(self.get_master_key()) self.keys = [(btc.bip32_ckd(m, 0), btc.bip32_ckd(m, 1)) for m in mixing_depth_keys] @@ -173,6 +169,22 @@ class Wallet(AbstractWallet): for i in range(self.max_mix_depth): self.index.append([0, 0]) + def get_master_key(self): + if not self.seed: + raise Exception("Cannot extract master key of wallet, no seed.") + return btc.bip32_master_key( + self.seed.decode('hex'),(btc.MAINNET_PRIVATE if get_network( + ) == 'mainnet' else btc.TESTNET_PRIVATE)) + + def get_mixing_depth_keys(self, master): + """legacy path is m/0/n for n 0..N mixing depths + """ + m_0 = btc.bip32_ckd(master, 0) + return [btc.bip32_ckd(m_0, c) for c in range(self.max_mix_depth)] + + def get_root_path(self): + return "m/0" + def entropy_to_seed(self, entropy): """for base/legacy wallet type, this is a passthrough. for bip39 style wallets, this will convert from one to the other @@ -375,7 +387,7 @@ class Bip39Wallet(Wallet): def entropy_to_seed(self, entropy): self.entropy = entropy.decode('hex') m = Mnemonic("english") - return m.to_seed(m.to_mnemonic(entropy)).encode('hex') + return m.to_seed(m.to_mnemonic(self.entropy)).encode('hex') class SegwitWallet(Bip39Wallet): @@ -387,6 +399,16 @@ class SegwitWallet(Bip39Wallet): wallet_dir=wallet_dir) self.vflag = JM_WALLET_SW_P2SH_P2WPKH + def get_root_path(self): + testflag = "1'" if get_network() == "testnet" else "0'" + return "m/44'/" + testflag + + def get_mixing_depth_keys(self, master): + pre_root = btc.bip32_ckd(master, 44 + 2**31) + testnet_flag = 1 if get_network() == "testnet" else 0 + root = btc.bip32_ckd(pre_root, testnet_flag + 2**31) + return [btc.bip32_ckd(root, c + 2**31) for c in range(self.max_mix_depth)] + def get_vbyte(self): return get_p2sh_vbyte() diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index c7b0953..e735fae 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -91,6 +91,7 @@ def bip32pathparse(path): return False elements = path.split(bip32sep)[1:] for e in elements: + if e[-1] == "'": e = e[:-1] try: x = int(e) except: @@ -103,7 +104,7 @@ def bip32pathparse(path): def test_bip32_pathparse(): assert bip32pathparse("m/2/1/0017") assert not bip32pathparse("n/1/1/1/1") - assert bip32pathparse("m/0/1/100/3/2/2/21/004/005") + assert bip32pathparse("m/0/1'/100'/3'/2/2/21/004/005") assert not bip32pathparse("m/0/0/00k") return True """ @@ -205,8 +206,8 @@ class WalletViewBranch(WalletViewBase): return self.serclass(entryseparator.join(lines)) def serialize_branch_header(self): - bippath = self.bip32path + bip32sep + str(self.account) + bip32sep + \ - str(self.forchange) + bippath = self.bip32path + bip32sep + str(self.account) + "'" + \ + bip32sep + str(self.forchange) + "'" assert bip32pathparse(bippath) start = "external addresses" if self.forchange == 0 else "internal addresses" if self.forchange == -1: @@ -215,12 +216,13 @@ class WalletViewBranch(WalletViewBase): class WalletViewAccount(WalletViewBase): def __init__(self, bip32path, account, branches=None, account_name="mixdepth", - serclass=str, custom_separator=None): + serclass=str, custom_separator=None, xpub=None): super(WalletViewAccount, self).__init__(bip32path, children=branches, serclass=serclass, custom_separator=custom_separator) self.account = account self.account_name = account_name + self.xpub = xpub if branches: assert len(branches) in [2, 3] #3 if imported keys assert all([isinstance(x, WalletViewBranch) for x in branches]) @@ -228,6 +230,8 @@ class WalletViewAccount(WalletViewBase): def serialize(self, entryseparator="\n"): header = self.account_name + self.separator + str(self.account) + if self.xpub: + header = header + self.separator + self.xpub footer = "Balance for mixdepth " + str( self.account) + ":" + self.separator + self.get_fmt_balance() return self.serclass(entryseparator.join([header] + [ @@ -289,6 +293,7 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False): then return its serialization directly """ acctlist = [] + rootpath = wallet.get_root_path() for m in range(wallet.max_mix_depth): branchlist = [] for forchange in [0, 1]: @@ -312,16 +317,20 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False): privkey = '' if (displayall or balance > 0 or (used == 'new' and forchange == 0)): - entrylist.append(WalletViewEntry("m/0", m, forchange, k, + entrylist.append(WalletViewEntry(rootpath, m, forchange, k, addr, [balance, balance], priv=privkey, used=used)) - branchlist.append(WalletViewBranch("m/0", m, forchange, entrylist, + branchlist.append(WalletViewBranch(rootpath, m, forchange, entrylist, xpub=xpub_key)) ipb = get_imported_privkey_branch(wallet, m, showprivkey) if ipb: branchlist.append(ipb) - acctlist.append(WalletViewAccount("m/0", m, branchlist)) - walletview = WalletView("m/0", acctlist) + #get the xpub key of the whole account + xpub_account = btc.bip32_privtopub( + wallet.get_mixing_depth_keys(wallet.get_master_key())[m]) + acctlist.append(WalletViewAccount(rootpath, m, branchlist, + xpub=xpub_account)) + walletview = WalletView(rootpath, acctlist) return walletview.serialize() def get_password_check(): @@ -400,9 +409,6 @@ def wallet_showseed(wallet): return "Wallet recovery seed\n\n" + m.to_mnemonic(wallet.entropy) + "\n" hexseed = wallet.seed print("hexseed = " + hexseed) - if bip39: - m = Mnemonic("english") - words = mn_encode(hexseed) return "Wallet recovery seed\n\n" + " ".join(words) + "\n" @@ -442,7 +448,7 @@ def wallet_dumpprivkey(wallet, hdpath): return hdpath + " is not a valid hd wallet path" def wallet_signmessage(wallet, hdpath, message): - if hdpath.startswith('m/0/'): + if hdpath.startswith(wallet.get_root_path()): m, forchange, k = [int(y) for y in hdpath[4:].split('/')] key = wallet.get_key(m, forchange, k) addr = btc.privkey_to_address(key, magicbyte=get_p2pk_vbyte())