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 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 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 decrypt_multisig_info(enc_multisig_info, password): """ LEGACY COMPATIBILITY CODE Given an encrypted multisig info dict, decrypt the sensitive fields. Returns {'private_keys': ..., 'redeem_script': ..., **other_fields} Return {'error': ...} on error """ from .backend.crypto.utils import aes_decrypt multisig_info = { 'private_keys': None, 'redeem_script': None, } hex_password = hexlify(password) assert is_encrypted_multisig(enc_multisig_info), 'Invalid encrypted multisig keys' multisig_info['private_keys'] = [] for enc_pk in enc_multisig_info['encrypted_private_keys']: pk = None try: pk = aes_decrypt(enc_pk, hex_password) ecdsa_private_key(pk) except Exception as e: if BLOCKSTACK_TEST or BLOCKSTACK_DEBUG: log.exception(e) return {'error': 'Invalid password; failed to decrypt private key in multisig wallet'} multisig_info['private_keys'].append(ecdsa_private_key(pk).to_hex()) redeem_script = None enc_redeem_script = enc_multisig_info['encrypted_redeem_script'] try: redeem_script = aes_decrypt(enc_redeem_script, hex_password) except Exception as e: if BLOCKSTACK_TEST or BLOCKSTACK_DEBUG: log.exception(e) return {'error': 'Invalid password; failed to decrypt redeem script in multisig wallet'} multisig_info['redeem_script'] = redeem_script # preserve any other information in the multisig info for k, v in enc_multisig_info.items(): if k not in ['encrypted_private_keys', 'encrypted_redeem_script']: multisig_info[k] = v assert virtualchain.is_multisig(multisig_info) return multisig_info
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 _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 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 decrypt_multisig_info(enc_multisig_info, password): """ LEGACY COMPATIBILITY CODE Given an encrypted multisig info dict, decrypt the sensitive fields. Returns {'private_keys': ..., 'redeem_script': ..., **other_fields} Return {'error': ...} on error """ from .backend.crypto.utils import aes_decrypt multisig_info = { 'private_keys': None, 'redeem_script': None, } hex_password = hexlify(password) assert is_encrypted_multisig( enc_multisig_info), 'Invalid encrypted multisig keys' multisig_info['private_keys'] = [] for enc_pk in enc_multisig_info['encrypted_private_keys']: pk = None try: pk = aes_decrypt(enc_pk, hex_password) ecdsa_private_key(pk) except Exception as e: if BLOCKSTACK_TEST or BLOCKSTACK_DEBUG: log.exception(e) return { 'error': 'Invalid password; failed to decrypt private key in multisig wallet' } multisig_info['private_keys'].append(ecdsa_private_key(pk).to_hex()) redeem_script = None enc_redeem_script = enc_multisig_info['encrypted_redeem_script'] try: redeem_script = aes_decrypt(enc_redeem_script, hex_password) except Exception as e: if BLOCKSTACK_TEST or BLOCKSTACK_DEBUG: log.exception(e) return { 'error': 'Invalid password; failed to decrypt redeem script in multisig wallet' } multisig_info['redeem_script'] = redeem_script # preserve any other information in the multisig info for k, v in enc_multisig_info.items(): if k not in ['encrypted_private_keys', 'encrypted_redeem_script']: multisig_info[k] = v assert virtualchain.is_multisig(multisig_info) return multisig_info
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")