Browse Source

add bip39 seeds for segwit wallets

master
Adam Gibson 9 years ago
parent
commit
95d7b7ef81
No known key found for this signature in database
GPG Key ID: B3AE09F1E9A3197A
  1. 2
      jmclient/jmclient/__init__.py
  2. 2
      jmclient/jmclient/configure.py
  3. 20
      jmclient/jmclient/wallet.py
  4. 85
      jmclient/jmclient/wallet_utils.py
  5. 2
      jmclient/setup.py

2
jmclient/jmclient/__init__.py

@ -18,7 +18,7 @@ from .slowaes import decryptData, encryptData
from .taker import Taker from .taker import Taker
from .wallet import (AbstractWallet, BitcoinCoreInterface, Wallet, from .wallet import (AbstractWallet, BitcoinCoreInterface, Wallet,
BitcoinCoreWallet, estimate_tx_fee, WalletError, 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, from .configure import (load_program_config, jm_single, get_p2pk_vbyte,
get_network, jm_single, get_network, validate_address, get_irc_mchannels, get_network, jm_single, get_network, validate_address, get_irc_mchannels,
check_utxo_blacklist, get_blockchain_interface_instance, get_p2sh_vbyte, check_utxo_blacklist, get_blockchain_interface_instance, get_p2sh_vbyte,

2
jmclient/jmclient/configure.py

@ -137,6 +137,8 @@ unconfirm_timeout_sec = 90
confirm_timeout_hours = 6 confirm_timeout_hours = 6
[POLICY] [POLICY]
#Use segwit style wallets and transactions
segwit = true
# for dust sweeping, try merge_algorithm = gradual # for dust sweeping, try merge_algorithm = gradual
# for more rapid dust sweeping, try merge_algorithm = greedy # for more rapid dust sweeping, try merge_algorithm = greedy
# for most rapid dust sweeping, try merge_algorithm = greediest # for most rapid dust sweeping, try merge_algorithm = greediest

20
jmclient/jmclient/wallet.py

@ -5,7 +5,7 @@ import pprint
import sys import sys
import datetime import datetime
from decimal import Decimal from decimal import Decimal
from mnemonic import Mnemonic
from ConfigParser import NoSectionError from ConfigParser import NoSectionError
from getpass import getpass from getpass import getpass
@ -153,7 +153,8 @@ class Wallet(AbstractWallet):
self.unspent = {} self.unspent = {}
self.spent_utxos = [] self.spent_utxos = []
self.imported_privkeys = {} 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: if not self.seed:
raise WalletError("Failed to decrypt wallet") raise WalletError("Failed to decrypt wallet")
if extend_mixdepth and len(self.index_cache) > max_mix_depth: 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): for i in range(self.max_mix_depth):
self.index.append([0, 0]) 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): def get_txtype(self):
"""Return string defining wallet type """Return string defining wallet type
for purposes of transaction size estimates 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)) log.debug('get_utxos_by_mixdepth = \n' + pprint.pformat(mix_utxo_list))
return 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, def __init__(self, seedarg, pwd, max_mix_depth=2, gaplimit=6,
extend_mixdepth=False, storepassword=False, wallet_dir=None): extend_mixdepth=False, storepassword=False, wallet_dir=None):
self.entropy = None
super(SegwitWallet, self).__init__(seedarg, pwd, max_mix_depth, gaplimit, super(SegwitWallet, self).__init__(seedarg, pwd, max_mix_depth, gaplimit,
extend_mixdepth, storepassword, extend_mixdepth, storepassword,
wallet_dir=wallet_dir) wallet_dir=wallet_dir)

85
jmclient/jmclient/wallet_utils.py

@ -4,10 +4,11 @@ import os
import pprint import pprint
import sys import sys
import datetime import datetime
from decimal import Decimal import binascii
from mnemonic import Mnemonic
from optparse import OptionParser from optparse import OptionParser
import getpass
from jmclient import (get_network, Wallet, from jmclient import (get_network, Wallet, Bip39Wallet,
encryptData, get_p2pk_vbyte, jm_single, encryptData, get_p2pk_vbyte, jm_single,
mn_decode, mn_encode, BitcoinCoreInterface, mn_decode, mn_encode, BitcoinCoreInterface,
JsonRpcError, sync_wallet, WalletError, SegwitWallet) JsonRpcError, sync_wallet, WalletError, SegwitWallet)
@ -323,27 +324,16 @@ def wallet_display(wallet, gaplimit, showprivkey, displayall=False):
walletview = WalletView("m/0", acctlist) walletview = WalletView("m/0", acctlist)
return walletview.serialize() return walletview.serialize()
def wallet_generate_recover(method, walletspath, default_wallet_name='wallet.json'): def get_password_check():
if method == 'generate': password = get_password('Enter wallet encryption passphrase: ')
seed = btc.sha256(os.urandom(64))[:32] password2 = get_password('Reenter wallet encryption passphrase: ')
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: ')
if password != password2: if password != password2:
print('ERROR. Passwords did not match') print('ERROR. Passwords did not match')
return False return False, False
password_key = btc.bin_dbl_sha256(password) 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") timestamp = datetime.datetime.now().strftime("%Y/%m/%d %H:%M:%S")
walletfile = json.dumps({'creator': 'joinmarket project', walletfile = json.dumps({'creator': 'joinmarket project',
'creation_time': timestamp, 'creation_time': timestamp,
@ -364,9 +354,55 @@ def wallet_generate_recover(method, walletspath, default_wallet_name='wallet.jso
print('saved to ' + walletname) print('saved to ' + walletname)
return True 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): 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 hexseed = wallet.seed
print("hexseed = " + hexseed) print("hexseed = " + hexseed)
if bip39:
m = Mnemonic("english")
words = mn_encode(hexseed) words = mn_encode(hexseed)
return "Wallet recovery seed\n\n" + " ".join(words) + "\n" return "Wallet recovery seed\n\n" + " ".join(words) + "\n"
@ -424,7 +460,8 @@ def wallet_tool_main(wallet_root_path):
""" """
parser = get_wallettool_parser() parser = get_wallettool_parser()
(options, args) = parser.parse_args() (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 # if the index_cache stored in wallet.json is longer than the default
# then set maxmixdepth to the length of index_cache # then set maxmixdepth to the length of index_cache
maxmixdepth_configured = True maxmixdepth_configured = True
@ -448,7 +485,7 @@ def wallet_tool_main(wallet_root_path):
seed = args[0] seed = args[0]
method = ('display' if len(args) == 1 else args[1].lower()) method = ('display' if len(args) == 1 else args[1].lower())
if not os.path.exists(os.path.join(wallet_root_path, seed)): 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, options.gaplimit, extend_mixdepth= not maxmixdepth_configured,
storepassword=(method == 'importprivkey'), storepassword=(method == 'importprivkey'),
wallet_dir=wallet_root_path) wallet_dir=wallet_root_path)
@ -456,7 +493,7 @@ def wallet_tool_main(wallet_root_path):
while True: while True:
try: try:
pwd = get_password("Enter wallet decryption passphrase: ") pwd = get_password("Enter wallet decryption passphrase: ")
wallet = SegwitWallet(seed, pwd, wallet = walletclass(seed, pwd,
options.maxmixdepth, options.maxmixdepth,
options.gaplimit, options.gaplimit,
extend_mixdepth=not maxmixdepth_configured, extend_mixdepth=not maxmixdepth_configured,

2
jmclient/setup.py

@ -9,5 +9,5 @@ setup(name='joinmarketclient',
author_email='ekaggata@gmail.com', author_email='ekaggata@gmail.com',
license='GPL', license='GPL',
packages=['jmclient'], packages=['jmclient'],
install_requires=['joinmarketbase==0.2.2'], install_requires=['joinmarketbase==0.2.2', 'mnemonic'],
zip_safe=False) zip_safe=False)

Loading…
Cancel
Save