You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

161 lines
4.9 KiB

#!/usr/bin/env python
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import argparse
import json
import os.path
from hashlib import sha256
from binascii import hexlify, unhexlify
from collections import defaultdict
from pyaes import AESModeOfOperationCBC, Decrypter
from jmclient import Storage, load_program_config
from jmclient.wallet_utils import get_password, get_wallet_cls,\
cli_get_wallet_passphrase_check, get_wallet_path
from jmbitcoin import wif_compressed_privkey
class ConvertException(Exception):
pass
def get_max_mixdepth(data):
return max(1, len(data.get('index_cache', [1])) - 1,
*data.get('imported', {}).keys())
def is_encrypted(wallet_data):
return 'encrypted_seed' in wallet_data or 'encrypted_entropy' in wallet_data
def double_sha256(plaintext):
return sha256(sha256(plaintext).digest()).digest()
def decrypt_data(key, data):
decrypter = Decrypter(AESModeOfOperationCBC(key, iv=data[:16]))
plain = decrypter.feed(data[16:])
plain += decrypter.feed()
return plain
def decrypt_entropy_extension(enc_data, key):
data = decrypt_data(key, unhexlify(enc_data))
if data[-9] != b'\xff':
raise ConvertException("Wrong password.")
chunks = data.split(b'\xff')
if len(chunks) < 3 or data[-8:] != hexlify(double_sha256(chunks[1]).decode('ascii')[:4]):
raise ConvertException("Wrong password.")
return chunks[1]
def decrypt_wallet_data(data, password):
key = double_sha256(password.encode('utf-8'))
enc_entropy = data.get('encrypted_seed') or data.get('encrypted_entropy')
enc_entropy_ext = data.get('encrypted_mnemonic_extension')
enc_imported = data.get('imported_keys')
entropy = decrypt_data(key, unhexlify(enc_entropy))
data['entropy'] = entropy
if enc_entropy_ext:
data['entropy_ext'] = decrypt_entropy_extension(enc_entropy_ext, key)
if enc_imported:
imported_keys = defaultdict(list)
for e in enc_imported:
md = int(e['mixdepth'])
imported_enc_key = unhexlify(e['encrypted_privkey'])
imported_key = decrypt_data(key, imported_enc_key)
imported_keys[md].append(imported_key)
data['imported'] = imported_keys
def new_wallet_from_data(data, file_name):
print("Creating new wallet file.")
new_pw = cli_get_wallet_passphrase_check()
if new_pw is False:
return False
storage = Storage(file_name, create=True, password=new_pw)
wallet_cls = get_wallet_cls()
kwdata = {
'entropy': data['entropy'],
'timestamp': data.get('creation_time'),
'max_mixdepth': get_max_mixdepth(data)
}
if 'entropy_ext' in data:
kwdata['entropy_extension'] = data['entropy_ext']
wallet_cls.initialize(storage, data['network'], **kwdata)
wallet = wallet_cls(storage)
if 'index_cache' in data:
for md, indices in enumerate(data['index_cache']):
wallet.set_next_index(md, 0, indices[0], force=True)
wallet.set_next_index(md, 1, indices[1], force=True)
if 'imported' in data:
for md in data['imported']:
for privkey in data['imported'][md]:
privkey += b'\x01'
wif = wif_compressed_privkey(hexlify(privkey).decode('ascii'))
wallet.import_private_key(md, wif)
wallet.save()
wallet.close()
return True
def parse_old_wallet(fh):
file_data = json.load(fh)
if is_encrypted(file_data):
pw = get_password("Enter password for old wallet file: ")
try:
decrypt_wallet_data(file_data, pw)
except ValueError:
print("Failed to open wallet: bad password")
return
except Exception as e:
print("Error: {}".format(e))
print("Failed to open wallet. Wrong password?")
return
return file_data
def main():
parser = argparse.ArgumentParser(
description="Convert old joinmarket json wallet format to new jmdat "
"format")
parser.add_argument('old_wallet_file', type=open)
parser.add_argument('--name', '-n', required=False, dest='name',
help="Name of the new wallet file. Default: [old wallet name].jmdat")
try:
args = parser.parse_args()
except Exception as e:
print("Error: {}".format(e))
return
data = parse_old_wallet(args.old_wallet_file)
if not data:
return
file_name = args.name or\
os.path.split(args.old_wallet_file.name)[-1].rsplit('.', 1)[0] + '.jmdat'
wallet_path = get_wallet_path(file_name, None)
if new_wallet_from_data(data, wallet_path):
print("New wallet file created at {}".format(wallet_path))
else:
print("Failed to convert wallet.")
if __name__ == '__main__':
load_program_config()
main()