From 95d7b7ef81e1f6bdd0fceb667b3dfc7638a55b20 Mon Sep 17 00:00:00 2001 From: Adam Gibson Date: Thu, 13 Jul 2017 15:17:16 +0300 Subject: [PATCH] add bip39 seeds for segwit wallets --- jmclient/jmclient/__init__.py | 2 +- jmclient/jmclient/configure.py | 2 + jmclient/jmclient/wallet.py | 20 ++++++-- jmclient/jmclient/wallet_utils.py | 85 ++++++++++++++++++++++--------- jmclient/setup.py | 2 +- 5 files changed, 82 insertions(+), 29 deletions(-) diff --git a/jmclient/jmclient/__init__.py b/jmclient/jmclient/__init__.py index a296c0d..ca9adf7 100644 --- a/jmclient/jmclient/__init__.py +++ b/jmclient/jmclient/__init__.py @@ -18,7 +18,7 @@ from .slowaes import decryptData, encryptData from .taker import Taker from .wallet import (AbstractWallet, BitcoinCoreInterface, Wallet, BitcoinCoreWallet, estimate_tx_fee, WalletError, - create_wallet_file, SegwitWallet) + create_wallet_file, SegwitWallet, Bip39Wallet) from .configure import (load_program_config, jm_single, get_p2pk_vbyte, get_network, jm_single, get_network, validate_address, get_irc_mchannels, check_utxo_blacklist, get_blockchain_interface_instance, get_p2sh_vbyte, diff --git a/jmclient/jmclient/configure.py b/jmclient/jmclient/configure.py index 669ec11..fe7d561 100644 --- a/jmclient/jmclient/configure.py +++ b/jmclient/jmclient/configure.py @@ -137,6 +137,8 @@ unconfirm_timeout_sec = 90 confirm_timeout_hours = 6 [POLICY] +#Use segwit style wallets and transactions +segwit = true # for dust sweeping, try merge_algorithm = gradual # for more rapid dust sweeping, try merge_algorithm = greedy # for most rapid dust sweeping, try merge_algorithm = greediest diff --git a/jmclient/jmclient/wallet.py b/jmclient/jmclient/wallet.py index 2f7ed06..5d09d61 100644 --- a/jmclient/jmclient/wallet.py +++ b/jmclient/jmclient/wallet.py @@ -5,7 +5,7 @@ import pprint import sys import datetime from decimal import Decimal - +from mnemonic import Mnemonic from ConfigParser import NoSectionError from getpass import getpass @@ -153,7 +153,8 @@ class Wallet(AbstractWallet): self.unspent = {} self.spent_utxos = [] self.imported_privkeys = {} - self.seed = self.read_wallet_file_data(seedarg, pwd, wallet_dir=wallet_dir) + self.seed = self.entropy_to_seed( + self.read_wallet_file_data(seedarg, pwd, wallet_dir=wallet_dir)) if not self.seed: raise WalletError("Failed to decrypt wallet") if extend_mixdepth and len(self.index_cache) > max_mix_depth: @@ -172,6 +173,12 @@ class Wallet(AbstractWallet): for i in range(self.max_mix_depth): self.index.append([0, 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 + """ + return entropy + def get_txtype(self): """Return string defining wallet type for purposes of transaction size estimates @@ -364,10 +371,17 @@ class Wallet(AbstractWallet): log.debug('get_utxos_by_mixdepth = \n' + pprint.pformat(mix_utxo_list)) return mix_utxo_list -class SegwitWallet(Wallet): +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') + +class SegwitWallet(Bip39Wallet): def __init__(self, seedarg, pwd, max_mix_depth=2, gaplimit=6, extend_mixdepth=False, storepassword=False, wallet_dir=None): + self.entropy = None super(SegwitWallet, self).__init__(seedarg, pwd, max_mix_depth, gaplimit, extend_mixdepth, storepassword, wallet_dir=wallet_dir) diff --git a/jmclient/jmclient/wallet_utils.py b/jmclient/jmclient/wallet_utils.py index ebc46a0..c7b0953 100644 --- a/jmclient/jmclient/wallet_utils.py +++ b/jmclient/jmclient/wallet_utils.py @@ -4,10 +4,11 @@ import os import pprint import sys import datetime -from decimal import Decimal +import binascii +from mnemonic import Mnemonic from optparse import OptionParser - -from jmclient import (get_network, Wallet, +import getpass +from jmclient import (get_network, Wallet, Bip39Wallet, encryptData, get_p2pk_vbyte, jm_single, mn_decode, mn_encode, BitcoinCoreInterface, JsonRpcError, sync_wallet, WalletError, SegwitWallet) @@ -323,27 +324,16 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False): walletview = WalletView("m/0", acctlist) return walletview.serialize() -def wallet_generate_recover(method, walletspath, default_wallet_name='wallet.json'): - if method == 'generate': - seed = btc.sha256(os.urandom(64))[:32] - words = mn_encode(seed) - print('Write down this wallet recovery seed\n\n' + ' '.join(words) + - '\n') - elif method == 'recover': - words = raw_input('Input 12 word recovery seed: ') - words = words.split() # default for split is 1 or more whitespace chars - if len(words) != 12: - print('ERROR: Recovery seed phrase must be exactly 12 words.') - return False - seed = mn_decode(words) - print(seed) - password = getpass.getpass('Enter wallet encryption passphrase: ') - password2 = getpass.getpass('Reenter wallet encryption passphrase: ') +def get_password_check(): + password = get_password('Enter wallet encryption passphrase: ') + password2 = get_password('Reenter wallet encryption passphrase: ') if password != password2: print('ERROR. Passwords did not match') - return False + return False, False password_key = btc.bin_dbl_sha256(password) - encrypted_seed = encryptData(password_key, seed.decode('hex')) + return password, password_key + +def persist_walletfile(walletspath, default_wallet_name, encrypted_seed): timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S") walletfile = json.dumps({'creator': 'joinmarket project', 'creation_time': timestamp, @@ -364,9 +354,55 @@ def wallet_generate_recover(method, walletspath, default_wallet_name='wallet.jso print('saved to ' + walletname) return True +def wallet_generate_recover_bip39(method, walletspath, default_wallet_name): + #using 128 bit entropy, 12 words, mnemonic module + m = Mnemonic("english") + if method == "generate": + words = m.generate() + print('Write down this wallet recovery seed\n\n' + words +'\n') + elif method == 'recover': + words = raw_input('Input 12 word recovery seed: ') + entropy = str(m.to_entropy(words)) + password, password_key = get_password_check() + if not password: + return False + encrypted_entropy = encryptData(password_key, entropy) + return persist_walletfile(walletspath, default_wallet_name, encrypted_entropy) + +def wallet_generate_recover(method, walletspath, + default_wallet_name='wallet.json'): + if jm_single().config.get("POLICY", "segwit") == "true": + return wallet_generate_recover_bip39(method, walletspath, default_wallet_name) + if method == 'generate': + seed = btc.sha256(os.urandom(64))[:32] + words = mn_encode(seed) + print('Write down this wallet recovery seed\n\n' + ' '.join(words) + + '\n') + elif method == 'recover': + words = raw_input('Input 12 word recovery seed: ') + words = words.split() # default for split is 1 or more whitespace chars + if len(words) != 12: + print('ERROR: Recovery seed phrase must be exactly 12 words.') + return False + seed = mn_decode(words) + print(seed) + password, password_key = get_password_check() + if not password: + return False + encrypted_seed = encryptData(password_key, seed.decode('hex')) + return persist_walletfile(walletspath, default_wallet_name, encrypted_seed) + def wallet_showseed(wallet): + if isinstance(wallet, Bip39Wallet): + if not wallet.entropy: + return "Entropy is not initialized." + m = Mnemonic("english") + 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" @@ -424,7 +460,8 @@ def wallet_tool_main(wallet_root_path): """ parser = get_wallettool_parser() (options, args) = parser.parse_args() - + walletclass = SegwitWallet if jm_single().config.get( + "POLICY", "segwit") == "true" else Wallet # if the index_cache stored in wallet.json is longer than the default # then set maxmixdepth to the length of index_cache maxmixdepth_configured = True @@ -448,7 +485,7 @@ def wallet_tool_main(wallet_root_path): seed = args[0] method = ('display' if len(args) == 1 else args[1].lower()) if not os.path.exists(os.path.join(wallet_root_path, seed)): - wallet = SegwitWallet(seed, None, options.maxmixdepth, + wallet = walletclass(seed, None, options.maxmixdepth, options.gaplimit, extend_mixdepth= not maxmixdepth_configured, storepassword=(method == 'importprivkey'), wallet_dir=wallet_root_path) @@ -456,7 +493,7 @@ def wallet_tool_main(wallet_root_path): while True: try: pwd = get_password("Enter wallet decryption passphrase: ") - wallet = SegwitWallet(seed, pwd, + wallet = walletclass(seed, pwd, options.maxmixdepth, options.gaplimit, extend_mixdepth=not maxmixdepth_configured, diff --git a/jmclient/setup.py b/jmclient/setup.py index 6dd4337..eafe6c1 100644 --- a/jmclient/setup.py +++ b/jmclient/setup.py @@ -9,5 +9,5 @@ setup(name='joinmarketclient', author_email='ekaggata@gmail.com', license='GPL', packages=['jmclient'], - install_requires=['joinmarketbase==0.2.2'], + install_requires=['joinmarketbase==0.2.2', 'mnemonic'], zip_safe=False)