def make_wallet_keys(data_privkey=None, owner_privkey=None, payment_privkey=None): """ For testing. DO NOT USE """ ret = { 'owner_privkey': None, 'data_privkey': None, 'payment_privkey': None, } if data_privkey is not None: if not virtualchain.is_singlesig(data_privkey): raise ValueError('Invalid data key info') pk_data = ecdsa_private_key(data_privkey).to_hex() ret['data_privkey'] = pk_data if owner_privkey is not None: if virtualchain.is_multisig(owner_privkey): pks = owner_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript(owner_privkey['redeem_script']) assert m <= len(pks) multisig_info = virtualchain.make_multisig_info(m, pks) ret['owner_privkey'] = multisig_info ret['owner_addresses'] = [virtualchain.get_privkey_address(multisig_info)] elif virtualchain.is_singlesig(owner_privkey): pk_owner = ecdsa_private_key(owner_privkey).to_hex() ret['owner_privkey'] = pk_owner ret['owner_addresses'] = [virtualchain.get_privkey_address(pk_owner)] else: raise ValueError('Invalid owner key info') if payment_privkey is None: return ret if virtualchain.is_multisig(payment_privkey): pks = payment_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript(payment_privkey['redeem_script']) assert m <= len(pks) multisig_info = virtualchain.make_multisig_info(m, pks) ret['payment_privkey'] = multisig_info ret['payment_addresses'] = [virtualchain.get_privkey_address(multisig_info)] elif virtualchain.is_singlesig(payment_privkey): pk_payment = ecdsa_private_key(payment_privkey).to_hex() ret['payment_privkey'] = pk_payment ret['payment_addresses'] = [virtualchain.get_privkey_address(pk_payment)] else: raise ValueError('Invalid payment key info') ret['data_pubkey'] = ecdsa_private_key(ret['data_privkey']).public_key().to_hex() ret['data_pubkeys'] = [ret['data_pubkey']] return ret
def get_privkey_info_params(privkey_info, config_path=CONFIG_PATH): """ Get the parameters that characterize a private key info bundle: the number of private keys, and the number of signatures required to make a valid transaction. * for single private keys, this is (1, 1) * for multisig info dicts, this is (m, n) Return (m, n) on success Return (None, None) on failure """ if privkey_info is None: from .backend.blockchain import get_block_height key_config = (2, 3) log.warning('No private key info given, assuming {} key config'.format(key_config)) return key_config if virtualchain.is_singlesig( privkey_info ): return (1, 1) elif virtualchain.is_multisig( privkey_info ): m, pubs = virtualchain.parse_multisig_redeemscript(privkey_info['redeem_script']) if m is None or pubs is None: return None, None return m, len(pubs) return None, None
def is_singlesig_hex(privkey_info): """ Does the given private key info represent a single signature bundle? (i.e. one private key)? """ return virtualchain.is_singlesig(privkey_info) and re.match( r"^[0-9a-fA-F]+$", privkey_info)
def get_privkey_info_params(privkey_info, config_path=CONFIG_PATH): """ Get the parameters that characterize a private key info bundle: the number of private keys, and the number of signatures required to make a valid transaction. * for single private keys, this is (1, 1) * for multisig info dicts, this is (m, n) Return (m, n) on success Return (None, None) on failure """ if privkey_info is None: from .backend.blockchain import get_block_height key_config = (2, 3) log.warning('No private key info given, assuming {} key config'.format( key_config)) return key_config if virtualchain.is_singlesig(privkey_info): return (1, 1) elif virtualchain.is_multisig(privkey_info): m, pubs = virtualchain.parse_multisig_redeemscript( privkey_info['redeem_script']) if m is None or pubs is None: return None, None return m, len(pubs) return None, None
def tx_estimate_signature_len_bytes(privkey_info): if virtualchain.is_singlesig(privkey_info): return 73 else: m, _ = virtualchain.parse_multisig_redeemscript( privkey_info['redeem_script'] ) siglengths = 74 * m scriptlen = len(privkey_info['redeem_script']) / 2 return 6 + scriptlen + siglengths
def tx_estimate_signature_len_bytes(privkey_info): if virtualchain.is_singlesig(privkey_info): return 73 else: m, _ = virtualchain.parse_multisig_redeemscript( privkey_info['redeem_script']) siglengths = 74 * m scriptlen = len(privkey_info['redeem_script']) / 2 return 6 + scriptlen + siglengths
def get_data_privkey(user_zonefile, wallet_keys=None, config_path=CONFIG_PATH): """ Get the data private key that matches this zonefile. * If the zonefile has a public key that this wallet does not have, then there is no data key. * If the zonefile does not have a public key, then: * if the data private key in the wallet matches the owner private key, then the wallet data key is the data key to use. (this is for legacy compatibility with onename.com, which does not create data keys for users) * otherwise, there is no data key Return the private key on success Return {'error': ...} if we could not find the key """ from .wallet import get_wallet from .user import user_zonefile_data_pubkey zonefile_data_pubkey = None try: # NOTE: uncompressed... zonefile_data_pubkey = user_zonefile_data_pubkey(user_zonefile) except ValueError: log.error('Multiple pubkeys defined in zone file') return {'error': 'Multiple data public keys in zonefile'} wallet_keys = {} if wallet_keys is None else wallet_keys if wallet_keys.get('data_privkey', None) is None: log.error('No data private key set') return {'error': 'No data private key in wallet keys'} wallet = get_wallet(config_path=CONFIG_PATH) if wallet_keys is None else wallet_keys assert wallet, 'Failed to get wallet' if not wallet.has_key('data_privkey'): log.error("No data private key in wallet") return {'error': 'No data private key in wallet'} data_privkey = wallet['data_privkey'] # NOTE: uncompresssed wallet_data_pubkey = keylib.key_formatting.decompress(get_pubkey_hex(str(data_privkey))) if zonefile_data_pubkey is None and wallet_data_pubkey is not None: # zone file does not have a data key set. # the wallet data key *must* match the owner key owner_privkey_info = wallet['owner_privkey'] owner_privkey = None if virtualchain.is_singlesig(owner_privkey_info): owner_privkey = owner_privkey_info elif virtualchain.is_multisig(owner_privkey_info): owner_privkey = owner_privkey_info['private_keys'][0] owner_pubkey = keylib.key_formatting.decompress(get_pubkey_hex(str(owner_privkey))) if owner_pubkey != wallet_data_pubkey: # doesn't match. no data key return {'error': 'No zone file key, and data key does not match owner key ({} != {})'.format(owner_pubkey, wallet_data_pubkey)} return str(data_privkey)
def privkey_to_string(privkey_info): """ Convert private key to string Return None on invalid """ if virtualchain.is_singlesig(privkey_info): return singlesig_privkey_to_string(privkey_info) if virtualchain.is_multisig(privkey_info): return multisig_privkey_to_string(privkey_info) return None
def privkey_to_string(privkey_info): """ Convert private key to string Return None on invalid """ if virtualchain.is_singlesig(privkey_info): return singlesig_privkey_to_string(privkey_info) if virtualchain.is_multisig(privkey_info): return multisig_privkey_to_string(privkey_info) return None
def privkey_to_string(privkey_info): """ Convert private key to string Return None on invalid """ if virtualchain.is_singlesig(privkey_info): return singlesig_privkey_to_string(privkey_info) elif isinstance(privkey_info, dict) and privkey_info.has_key('private_keys'): return multisig_privkey_to_string(privkey_info) return None
def get_data_key_from_owner_key_LEGACY(owner_privkey): """ Given the owner private key, select a data private key to use. THIS IS ONLY FOR LEGACY CLIENTS THAT DO NOT HAVE DATA PRIVATE KEYS DEFINED IN THEIR WALLETS. """ data_privkey = None if virtualchain.is_singlesig(owner_privkey): data_privkey = owner_privkey else: # data private key gets instantiated from the first owner private key, # if we have a multisig key bundle. data_privkey = owner_privkey['private_keys'][0] return data_privkey
def get_data_key_from_owner_key_LEGACY(owner_privkey): """ Given the owner private key, select a data private key to use. THIS IS ONLY FOR LEGACY CLIENTS THAT DO NOT HAVE DATA PRIVATE KEYS DEFINED IN THEIR WALLETS. """ data_privkey = None if virtualchain.is_singlesig(owner_privkey): data_privkey = owner_privkey else: # data private key gets instantiated from the first owner private key, # if we have a multisig key bundle. data_privkey = owner_privkey['private_keys'][0] return data_privkey
def make_legacy_wallet_013_keys(data, password): """ Given a legacy 0.13 wallet with "owner private key" and "payment private key" defined, generate the owner, payment, and data values. In these wallets, the data key is the same as the owner key. Return {'payment': priv, 'owner': priv, 'data': priv} on success Return {'error': ...} on error """ payment_privkey = decrypt_private_key_info( data['encrypted_payment_privkey'], password) owner_privkey = decrypt_private_key_info(data['encrypted_owner_privkey'], password) err = None if 'error' in payment_privkey: err = payment_privkey['error'] else: payment_privkey = payment_privkey.pop('private_key_info') if 'error' in owner_privkey: err = owner_privkey['error'] else: owner_privkey = owner_privkey.pop('private_key_info') if err: ret = {'error': "Failed to decrypt owner and payment keys"} log.debug("Failed to decrypt owner or payment keys: {}".format(err)) return ret data_privkey = None if virtualchain.is_singlesig(owner_privkey): data_privkey = virtualchain.get_singlesig_privkey(owner_privkey) else: # data private key gets instantiated from the first owner private key, # if we have a multisig key bundle. data_privkey = owner_privkey['private_keys'][0] key_defaults = { 'payment': payment_privkey, 'owner': owner_privkey, 'data': data_privkey } return key_defaults
def make_legacy_wallet_013_keys(data, password): """ Given a legacy 0.13 wallet with "owner private key" and "payment private key" defined, generate the owner, payment, and data values. In these wallets, the data key is the same as the owner key. Return {'payment': priv, 'owner': priv, 'data': priv} on success Return {'error': ...} on error """ payment_privkey = decrypt_private_key_info(data['encrypted_payment_privkey'], password) owner_privkey = decrypt_private_key_info(data['encrypted_owner_privkey'], password) err = None if 'error' in payment_privkey: err = payment_privkey['error'] else: payment_privkey = payment_privkey.pop('private_key_info') if 'error' in owner_privkey: err = owner_privkey['error'] else: owner_privkey = owner_privkey.pop('private_key_info') if err: ret = {'error': "Failed to decrypt owner and payment keys"} log.debug("Failed to decrypt owner or payment keys: {}".format(err)) return ret data_privkey = None if virtualchain.is_singlesig(owner_privkey): data_privkey = owner_privkey else: # data private key gets instantiated from the first owner private key, # if we have a multisig key bundle. data_privkey = owner_privkey['private_keys'][0] key_defaults = { 'payment': payment_privkey, 'owner': owner_privkey, 'data': data_privkey } return key_defaults
def _convert_key(given_privkey, key_type): if virtualchain.is_multisig(given_privkey): pks = given_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( given_privkey['redeem_script']) assert m <= len(pks) multisig_info = virtualchain.make_multisig_info(m, pks) ret['{}_privkey'.format(key_type)] = multisig_info ret['{}_addresses'.format(key_type)] = [ virtualchain.get_privkey_address(multisig_info) ] elif virtualchain.is_singlesig(given_privkey): pk = ecdsa_private_key(given_privkey).to_hex() ret['{}_privkey'.format(key_type)] = pk ret['{}_addresses'.format(key_type)] = [ virtualchain.get_privkey_address(pk) ] elif virtualchain.btc_is_singlesig_segwit(given_privkey): pk = virtualchain.make_segwit_info( virtualchain.get_singlesig_privkey(given_privkey)) ret['{}_privkey'.format(key_type)] = pk ret['{}_addresses'.format(key_type)] = [pk['address']] elif virtualchain.btc_is_multisig_segwit(given_privkey): pks = given_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( given_privkey['redeem_script']) assert m <= len(pks) pk = virtualchain.make_multisig_segwit_info(m, pks) ret['{}_privkey'.format(key_type)] = pk ret['{}_addresses'.format(key_type)] = [pk['address']] else: raise ValueError('Invalid owner key info')
def make_wallet_keys(data_privkey=None, owner_privkey=None, payment_privkey=None): """ For testing. DO NOT USE """ ret = { 'owner_privkey': None, 'data_privkey': None, 'payment_privkey': None, } def _convert_key(given_privkey, key_type): if virtualchain.is_multisig(given_privkey): pks = given_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( given_privkey['redeem_script']) assert m <= len(pks) multisig_info = virtualchain.make_multisig_info(m, pks) ret['{}_privkey'.format(key_type)] = multisig_info ret['{}_addresses'.format(key_type)] = [ virtualchain.get_privkey_address(multisig_info) ] elif virtualchain.is_singlesig(given_privkey): pk = ecdsa_private_key(given_privkey).to_hex() ret['{}_privkey'.format(key_type)] = pk ret['{}_addresses'.format(key_type)] = [ virtualchain.get_privkey_address(pk) ] elif virtualchain.btc_is_singlesig_segwit(given_privkey): pk = virtualchain.make_segwit_info( virtualchain.get_singlesig_privkey(given_privkey)) ret['{}_privkey'.format(key_type)] = pk ret['{}_addresses'.format(key_type)] = [pk['address']] elif virtualchain.btc_is_multisig_segwit(given_privkey): pks = given_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( given_privkey['redeem_script']) assert m <= len(pks) pk = virtualchain.make_multisig_segwit_info(m, pks) ret['{}_privkey'.format(key_type)] = pk ret['{}_addresses'.format(key_type)] = [pk['address']] else: raise ValueError('Invalid owner key info') if data_privkey is not None: if not virtualchain.is_singlesig(data_privkey): raise ValueError('Invalid data key info') pk_data = ecdsa_private_key(data_privkey).to_hex() ret['data_privkey'] = pk_data if owner_privkey is not None: _convert_key(owner_privkey, 'owner') if payment_privkey is None: return ret _convert_key(payment_privkey, 'payment') ret['data_pubkey'] = ecdsa_private_key( ret['data_privkey']).public_key().to_hex() ret['data_pubkeys'] = [ret['data_pubkey']] return ret
def get_data_privkey(user_zonefile, wallet_keys=None, config_path=CONFIG_PATH): """ Get the data private key that matches this zonefile. * If the zonefile has a public key that this wallet does not have, then there is no data key. * If the zonefile does not have a public key, then: * if the data private key in the wallet matches the owner private key, then the wallet data key is the data key to use. (this is for legacy compatibility with onename.com, which does not create data keys for users) * otherwise, there is no data key Return the private key on success Return {'error': ...} if we could not find the key """ from .wallet import get_wallet from .user import user_zonefile_data_pubkey zonefile_data_pubkey = None try: # NOTE: uncompressed... zonefile_data_pubkey = user_zonefile_data_pubkey(user_zonefile) except ValueError: log.error('Multiple pubkeys defined in zone file') return {'error': 'Multiple data public keys in zonefile'} wallet_keys = {} if wallet_keys is None else wallet_keys if wallet_keys.get('data_privkey', None) is None: log.error('No data private key set') return {'error': 'No data private key in wallet keys'} wallet = get_wallet( config_path=CONFIG_PATH) if wallet_keys is None else wallet_keys assert wallet, 'Failed to get wallet' if not wallet.has_key('data_privkey'): log.error("No data private key in wallet") return {'error': 'No data private key in wallet'} data_privkey = wallet['data_privkey'] # NOTE: uncompresssed wallet_data_pubkey = keylib.key_formatting.decompress( get_pubkey_hex(str(data_privkey))) if zonefile_data_pubkey is None and wallet_data_pubkey is not None: # zone file does not have a data key set. # the wallet data key *must* match the owner key owner_privkey_info = wallet['owner_privkey'] owner_privkey = None if virtualchain.is_singlesig(owner_privkey_info): owner_privkey = owner_privkey_info elif virtualchain.is_multisig(owner_privkey_info): owner_privkey = owner_privkey_info['private_keys'][0] owner_pubkey = keylib.key_formatting.decompress( get_pubkey_hex(str(owner_privkey))) if owner_pubkey != wallet_data_pubkey: # doesn't match. no data key return { 'error': 'No zone file key, and data key does not match owner key ({} != {})' .format(owner_pubkey, wallet_data_pubkey) } return str(data_privkey)
def make_wallet_keys(data_privkey=None, owner_privkey=None, payment_privkey=None): """ For testing. DO NOT USE """ ret = { 'owner_privkey': None, 'data_privkey': None, 'payment_privkey': None, } if data_privkey is not None: if not virtualchain.is_singlesig(data_privkey): raise ValueError('Invalid data key info') pk_data = ecdsa_private_key(data_privkey).to_hex() ret['data_privkey'] = pk_data if owner_privkey is not None: if virtualchain.is_multisig(owner_privkey): pks = owner_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( owner_privkey['redeem_script']) assert m <= len(pks) multisig_info = virtualchain.make_multisig_info(m, pks) ret['owner_privkey'] = multisig_info ret['owner_addresses'] = [ virtualchain.get_privkey_address(multisig_info) ] elif virtualchain.is_singlesig(owner_privkey): pk_owner = ecdsa_private_key(owner_privkey).to_hex() ret['owner_privkey'] = pk_owner ret['owner_addresses'] = [ virtualchain.get_privkey_address(pk_owner) ] else: raise ValueError('Invalid owner key info') if payment_privkey is None: return ret if virtualchain.is_multisig(payment_privkey): pks = payment_privkey['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( payment_privkey['redeem_script']) assert m <= len(pks) multisig_info = virtualchain.make_multisig_info(m, pks) ret['payment_privkey'] = multisig_info ret['payment_addresses'] = [ virtualchain.get_privkey_address(multisig_info) ] elif virtualchain.is_singlesig(payment_privkey): pk_payment = ecdsa_private_key(payment_privkey).to_hex() ret['payment_privkey'] = pk_payment ret['payment_addresses'] = [ virtualchain.get_privkey_address(pk_payment) ] else: raise ValueError('Invalid payment key info') ret['data_pubkey'] = ecdsa_private_key( ret['data_privkey']).public_key().to_hex() ret['data_pubkeys'] = [ret['data_pubkey']] return ret
def encrypt_wallet(decrypted_wallet, password, test_legacy=False): """ Encrypt the wallet. Return the encrypted dict on success Return {'error': ...} on error """ if test_legacy: assert BLOCKSTACK_TEST, 'test_legacy only works in test mode' # must be conformant to the current schema if not test_legacy: jsonschema.validate(decrypted_wallet, WALLET_SCHEMA_CURRENT) owner_address = virtualchain.get_privkey_address(decrypted_wallet['owner_privkey']) payment_address = virtualchain.get_privkey_address(decrypted_wallet['payment_privkey']) data_pubkey = None data_privkey_info = None if decrypted_wallet.has_key('data_privkey'): # make sure data key is hex encoded data_privkey_info = decrypted_wallet.get('data_privkey', None) if not test_legacy: assert data_privkey_info if data_privkey_info: if not is_singlesig_hex(data_privkey_info): data_privkey_info = ecdsa_private_key(data_privkey_info).to_hex() if not virtualchain.is_singlesig(data_privkey_info): log.error('Invalid data private key') return {'error': 'Invalid data private key'} data_pubkey = ecdsa_private_key(data_privkey_info).public_key().to_hex() wallet = { 'owner_addresses': [owner_address], 'payment_addresses': decrypted_wallet['payment_addresses'], 'version': decrypted_wallet['version'], 'enc': None, # to be filled in } if data_pubkey: wallet['data_pubkey'] = data_pubkey wallet['data_pubkeys'] = [data_pubkey] wallet_enc = { 'owner_privkey': decrypted_wallet['owner_privkey'], 'payment_privkey': decrypted_wallet['payment_privkey'], } if data_privkey_info: wallet_enc['data_privkey'] = data_privkey_info # extra sanity check: make sure that when re-combined with the wallet, # we're still valid recombined_wallet = copy.deepcopy(wallet) recombined_wallet.update(wallet_enc) try: jsonschema.validate(recombined_wallet, WALLET_SCHEMA_CURRENT) except ValidationError as ve: if test_legacy: # no data key is allowed if we're testing the absence of a data key jsonschema.validate(recombined_wallet, WALLET_SCHEMA_CURRENT_NODATAKEY) else: raise # good to go! # encrypt secrets wallet_secret_str = json.dumps(wallet_enc, sort_keys=True) password_hex = hexlify(password) encrypted_secret_str = aes_encrypt(wallet_secret_str, password_hex) # fulfill wallet wallet['enc'] = encrypted_secret_str # sanity check try: jsonschema.validate(wallet, ENCRYPTED_WALLET_SCHEMA_CURRENT) except ValidationError as ve: if test_legacy: jsonschema.validate(wallet, ENCRYPTED_WALLET_SCHEMA_CURRENT_NODATAKEY) else: raise return wallet
def get_compressed_and_decompressed_private_key_info(privkey_info): """ Get the compressed and decompressed versions of private keys and addresses Return {'compressed_addr': ..., 'compressed_private_key_info': ..., 'decompressed_addr': ..., 'decompressed_private_key_info': ...} on success """ if virtualchain.is_multisig( privkey_info) or virtualchain.btc_is_multisig_segwit(privkey_info): # get both compressed and decompressed addresses privkeys = privkey_info['private_keys'] m, _ = virtualchain.parse_multisig_redeemscript( privkey_info['redeem_script']) privkeys_hex = [ecdsa_private_key(pk).to_hex() for pk in privkeys] decompressed_privkeys = map( lambda pk: pk if len(pk) == 64 else pk[:-2], privkeys_hex) compressed_privkeys = map( lambda pk: pk if len(pk) == 66 and pk[:-2] == '01' else pk, privkeys_hex) decompressed_multisig = virtualchain.make_multisig_info( m, decompressed_privkeys, compressed=True) compressed_multisig = virtualchain.make_multisig_info( m, compressed_privkeys, compressed=False) decompressed_addr = virtualchain.address_reencode( decompressed_multisig['address']) compressed_addr = virtualchain.address_reencode( compressed_multisig['address']) return { 'decompressed_private_key_info': decompressed_multisig, 'compressed_private_key_info': compressed_multisig, 'compressed_addr': compressed_addr, 'decompressed_addr': decompressed_addr } elif virtualchain.is_singlesig( privkey_info) or virtualchain.btc_is_singlesig_segwit( privkey_info): pk = virtualchain.get_singlesig_privkey(privkey_info) # get both compressed and decompressed addresses compressed_pk = None decompressed_pk = None if len(pk) == 66 and pk.endswith('01'): compressed_pk = pk decompressed_pk = pk[:-2] else: compressed_pk = pk decompressed_pk = pk + '01' compressed_pubk = ecdsa_private_key( compressed_pk).public_key().to_hex() decompressed_pubk = ecdsa_private_key( decompressed_pk).public_key().to_hex() compressed_addr = virtualchain.address_reencode( keylib.public_key_to_address(compressed_pubk)) decompressed_addr = virtualchain.address_reencode( keylib.public_key_to_address(decompressed_pubk)) return { 'decompressed_private_key_info': decompressed_pk, 'compressed_private_key_info': compressed_pk, 'compressed_addr': compressed_addr, 'decompressed_addr': decompressed_addr } else: raise ValueError("Invalid key bundle")
def is_singlesig_hex(privkey_info): """ Does the given private key info represent a single signature bundle? (i.e. one private key)? """ return virtualchain.is_singlesig(privkey_info) and re.match(r"^[0-9a-fA-F]+$", privkey_info)
def encrypt_wallet(decrypted_wallet, password, test_legacy=False): """ Encrypt the wallet. Return the encrypted dict on success Return {'error': ...} on error """ if test_legacy: assert BLOCKSTACK_TEST, 'test_legacy only works in test mode' # must be conformant to the current schema if not test_legacy: jsonschema.validate(decrypted_wallet, WALLET_SCHEMA_CURRENT) owner_address = virtualchain.get_privkey_address(decrypted_wallet['owner_privkey']) payment_address = virtualchain.get_privkey_address(decrypted_wallet['payment_privkey']) data_pubkey = None data_privkey_info = None if decrypted_wallet.has_key('data_privkey'): # make sure data key is hex encoded data_privkey_info = decrypted_wallet.get('data_privkey', None) if not test_legacy: assert data_privkey_info if data_privkey_info: if not is_singlesig_hex(data_privkey_info): data_privkey_info = ecdsa_private_key(data_privkey_info).to_hex() if not virtualchain.is_singlesig(data_privkey_info): log.error('Invalid data private key') return {'error': 'Invalid data private key'} data_pubkey = ecdsa_private_key(data_privkey_info).public_key().to_hex() wallet = { 'owner_addresses': [owner_address], 'payment_addresses': decrypted_wallet['payment_addresses'], 'version': decrypted_wallet['version'], 'enc': None, # to be filled in } if data_pubkey: wallet['data_pubkey'] = data_pubkey wallet['data_pubkeys'] = [data_pubkey] wallet_enc = { 'owner_privkey': decrypted_wallet['owner_privkey'], 'payment_privkey': decrypted_wallet['payment_privkey'], } if data_privkey_info: wallet_enc['data_privkey'] = data_privkey_info # extra sanity check: make sure that when re-combined with the wallet, # we're still valid recombined_wallet = copy.deepcopy(wallet) recombined_wallet.update(wallet_enc) try: jsonschema.validate(recombined_wallet, WALLET_SCHEMA_CURRENT) except ValidationError as ve: if test_legacy: # no data key is allowed if we're testing the absence of a data key jsonschema.validate(recombined_wallet, WALLET_SCHEMA_CURRENT_NODATAKEY) else: raise # good to go! # encrypt secrets wallet_secret_str = json.dumps(wallet_enc, sort_keys=True) password_hex = hexlify(password) encrypted_secret_str = aes_encrypt(wallet_secret_str, password_hex) # fulfill wallet wallet['enc'] = encrypted_secret_str # sanity check try: jsonschema.validate(wallet, ENCRYPTED_WALLET_SCHEMA_CURRENT) except ValidationError as ve: if test_legacy: jsonschema.validate(wallet, ENCRYPTED_WALLET_SCHEMA_CURRENT_NODATAKEY) else: raise return wallet