Esempio n. 1
0
def load_data_pubkey_for_new_zonefile(wallet_keys={}, config_path=CONFIG_PATH):
    """
    Find the right public key to use for data when creating a new zonefile.
    If the wallet has a data keypair defined, use that.
    Otherwise, fall back to the owner public key
    """
    data_pubkey = None

    data_privkey = wallet_keys.get('data_privkey', None)
    if data_privkey is not None:
        # force compressed
        data_pubkey = ecdsa_private_key(data_privkey,
                                        compressed=True).public_key().to_hex()
        return data_pubkey

    data_pubkey = wallet_keys.get('data_pubkey', None)
    return data_pubkey
Esempio n. 2
0
def decrypt_private_key_info(privkey_info, password):
    """
    LEGACY COMPATIBILITY CODE

    Decrypt a particular private key info bundle.
    It can be either a single-signature private key, or a multisig key bundle.
    Return {'address': ..., 'private_key_info': ...} on success.
    Return {'error': ...} on error.
    """

    from .backend.crypto.utils import aes_decrypt

    ret = {}
    if is_encrypted_multisig(privkey_info):
        ret = decrypt_multisig_info(privkey_info, password)
        if 'error' in ret:
            return {
                'error':
                'Failed to decrypt multisig wallet: {}'.format(ret['error'])
            }

        address = virtualchain.get_privkey_address(ret)
        return {'address': address, 'private_key_info': ret}

    if is_encrypted_singlesig(privkey_info):
        try:
            hex_password = hexlify(password)
            pk = aes_decrypt(privkey_info, hex_password)
            pk = ecdsa_private_key(pk).to_hex()
        except Exception as e:
            if BLOCKSTACK_TEST:
                log.exception(e)

            return {'error': 'Invalid password'}

        address = virtualchain.get_privkey_address(pk)
        return {'address': address, 'private_key_info': pk}

    return {'error': 'Invalid encrypted private key info'}
Esempio n. 3
0
    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')
Esempio n. 4
0
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
Esempio n. 5
0
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
Esempio n. 6
0
def singlesig_privkey_to_string(privkey_info):
    """
    Convert private key to string
    """
    return ecdsa_private_key(privkey_info).to_hex()
Esempio n. 7
0
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)

    scrypt_kwargs = {}
    if os.environ.get("BLOCKSTACK_TEST") == "1" and os.environ.get('BLOCKSTACK_CLIENT_WALLET_CRYPTO_PARAMS') is not None:
        scrypt_kwargs = json.loads(os.environ["BLOCKSTACK_CLIENT_WALLET_CRYPTO_PARAMS"])

    encrypted_secret_str = aes_encrypt(wallet_secret_str, password_hex, **scrypt_kwargs)

    # 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
Esempio n. 8
0
def decrypt_wallet(data, password, config_path=CONFIG_PATH):
    """
    Decrypt a wallet's encrypted fields.  The wallet will be migrated to the current schema.

    Migrate the wallet from a legacy format to the latest format, if needed.

    Return {'status': True, 'migrated': True|False, 'wallet': wallet} on success.  
    Return {'error': ...} on failure
    """

    wallet_info = inspect_wallet_data(data)
    if 'error' in wallet_info:
        return wallet_info

    legacy = (wallet_info['format'] == 'legacy')
    legacy_013 = (wallet_info['format'] == 'legacy_013')
    legacy_014 = (wallet_info['format'] == 'legacy_014')
    migrated = wallet_info['migrate']

    any_legacy = (legacy or legacy_013 or legacy_014)

    legacy_hdwallet = None
    key_defaults = {}
    new_wallet = {}
    ret = {}
    
    # version check 
    # if the version has changed, we'll need to potentially migrate
    # to e.g. trigger a re-encryption 
    if not data.has_key('version'):
        log.debug("Wallet has no version; triggering migration")

    elif data['version'] != SERIES_VERSION:
        log.debug("Wallet series has changed from {} to {}; triggerring migration".format(data['version'], SERIES_VERSION))

    # legacy check
    if legacy:
        # legacy wallets use a hierarchical deterministic private key for owner, payment, and data keys.
        # get that key first, if needed.
        key_defaults = make_legacy_wallet_keys(data, password)
        if 'error' in key_defaults:
            log.error("Failed to migrate legacy wallet: {}".format(key_defaults['error']))
            return key_defaults

    elif legacy_013:
        # legacy 0.13 wallets have an owner_privkey and a payment_privkey, but not a data_privkey
        key_defaults = make_legacy_wallet_013_keys(data, password)
        if 'error' in key_defaults:
            log.error("Failed to migrate legacy 0.13 wallet: {}".format(key_defaults['error']))
            return key_defaults

    if any_legacy:
        wallet_info = decrypt_wallet_legacy(data, key_defaults, password)

    else:
        wallet_info = decrypt_wallet_current(data, password)

        # No matter what we do, do not save this wallet if it is current.
        # First, it's not necessary if the wallet is not legacy.
        # Second, the data private key is dynamically filled-in for data-key-less wallets,
        # and we do not want to preserve this (i.e. we want the user to select a data key
        # and switch over to using it).
        migrated = False

    if 'error' in wallet_info:
        log.error("Failed to decrypt wallet; {}".format(wallet_info['error']))
        return {'error': 'Failed to decrypt wallet'}

    new_wallet = wallet_info['wallet']

    # post-decryption formatting
    # make sure data key is an uncompressed public key
    assert new_wallet.has_key('data_privkey')
    data_pubkey = ecdsa_private_key(str(new_wallet['data_privkey'])).public_key().to_hex()
    if keylib.key_formatting.get_pubkey_format(data_pubkey) == 'hex_compressed':
        data_pubkey = keylib.key_formatting.decompress(data_pubkey)

    data_pubkey = str(data_pubkey)

    new_wallet['data_pubkeys'] = [data_pubkey]
    new_wallet['data_pubkey'] = data_pubkey

    # pass along version
    new_wallet['version'] = SERIES_VERSION

    # sanity check--must be decrypted properly
    try:
        jsonschema.validate(new_wallet, WALLET_SCHEMA_CURRENT)
    except ValidationError as e:
        log.exception(e)
        log.error("FATAL: BUG: invalid wallet generated")
        os.abort()

    ret = {
        'status': True,
        'wallet': new_wallet,
        'migrated': migrated
    }

    return ret
Esempio n. 9
0
def make_wallet(password, payment_privkey_info=None, owner_privkey_info=None, data_privkey_info=None, test_legacy=False, encrypt=True, segwit=None):
    """
    Make a new, encrypted wallet structure.
    The owner and payment keys will be 2-of-3 multisig key bundles.
    The data keypair will be a single-key bundle.

    Return the new wallet on success.
    Return {'error': ...} on failure
    """

    if test_legacy and not BLOCKSTACK_TEST:
        raise Exception("Not in testing but tried to make a legacy wallet")

    if segwit is None:
        # no preference given.
        # safe to use by default post-F-day 2017 (Dec 1 2017)
        '''
        if time.time() >= 1512086400:
            segwit = True

        else:
            # defer to virtualchain
            segwit = virtualchain.get_features('segwit')
        '''
        # disable for now
        segwit = False

    # default to 2-of-3 multisig key info if data isn't given
    if segwit:
        payment_privkey_info = virtualchain.make_multisig_segwit_wallet(2,3) if payment_privkey_info is None and not test_legacy else payment_privkey_info
        owner_privkey_info = virtualchain.make_multisig_segwit_wallet(2,3) if owner_privkey_info is None and not test_legacy else owner_privkey_info

    else:
        payment_privkey_info = virtualchain.make_multisig_wallet(2,3) if payment_privkey_info is None and not test_legacy else payment_privkey_info
        owner_privkey_info = virtualchain.make_multisig_wallet(2,3) if owner_privkey_info is None and not test_legacy else owner_privkey_info

    data_privkey_info = ecdsa_private_key().to_hex() if data_privkey_info is None and not test_legacy else data_privkey_info

    decrypted_wallet = {
        'owner_addresses': [virtualchain.get_privkey_address(owner_privkey_info)],
        'owner_privkey': owner_privkey_info,
        'payment_addresses': [virtualchain.get_privkey_address(payment_privkey_info)],
        'payment_privkey': payment_privkey_info,
        'data_pubkey': ecdsa_private_key(data_privkey_info).public_key().to_hex(),
        'data_pubkeys': [ecdsa_private_key(data_privkey_info).public_key().to_hex()],
        'data_privkey': data_privkey_info,
        'version': SERIES_VERSION,
    }

    if not test_legacy:
        jsonschema.validate(decrypted_wallet, WALLET_SCHEMA_CURRENT)

    if encrypt:
        encrypted_wallet = encrypt_wallet(decrypted_wallet, password, test_legacy=test_legacy)
        if 'error' in encrypted_wallet:
            return encrypted_wallet

        # sanity check
        try:
            jsonschema.validate(encrypted_wallet, ENCRYPTED_WALLET_SCHEMA_CURRENT)
        except ValidationError as ve:
            if test_legacy:
                # no data key is permitted 
                assert BLOCKSTACK_TEST
                jsonschema.validate(encrypted_wallet, ENCRYPTED_WALLET_SCHEMA_CURRENT_NODATAKEY)
            else:
                raise

        return encrypted_wallet

    else:
        return decrypted_wallet
Esempio n. 10
0
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
Esempio n. 11
0
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")