Example #1
0
def test_encrypt_odd_length_data():
    blob = Blob(get_random_value_hex(64)[-1])
    key = get_random_value_hex(32)

    try:
        Cryptographer.encrypt(blob, key)
        assert False

    except ValueError:
        assert True
Example #2
0
def test_encrypt_wrong_key_size():
    blob = Blob(get_random_value_hex(64))
    key = get_random_value_hex(31)

    try:
        Cryptographer.encrypt(blob, key)
        assert False

    except ValueError:
        assert True
def test_add_appointment_trigger_on_cache_cannot_decrypt(bitcoin_cli):
    commitment_tx, penalty_tx = create_txs(bitcoin_cli)

    # Let's send the commitment to the network and mine a block
    broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, bitcoin_cli.getnewaddress())
    sleep(1)

    # The appointment data is built using a random 32-byte value.
    appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx)

    # We cannot use teos_cli.add_appointment here since it computes the locator internally, so let's do it manually.
    appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"))
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32))
    appointment = Appointment.from_dict(appointment_data)

    signature = Cryptographer.sign(appointment.serialize(), user_sk)
    data = {"appointment": appointment.to_dict(), "signature": signature}

    # Send appointment to the server.
    response = teos_cli.post_request(data, teos_add_appointment_endpoint)
    response_json = teos_cli.process_post_response(response)

    # Check that the server has accepted the appointment
    signature = response_json.get("signature")
    rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
    assert teos_id == Cryptographer.get_compressed_pk(rpk)
    assert response_json.get("locator") == appointment.locator

    # The appointment should should have been inmediately dropped
    with pytest.raises(TowerResponseError):
        get_appointment_info(appointment_data["locator"])
Example #4
0
def test_add_appointment_in_cache_invalid_transaction(api, client):
    api.watcher.gatekeeper.registered_users[user_id] = UserInfo(available_slots=1, subscription_expiry=0)

    # We need to create the appointment manually
    dispute_tx = create_dummy_transaction()
    dispute_txid = dispute_tx.tx_id.hex()
    penalty_tx = create_dummy_transaction(dispute_txid)

    locator = compute_locator(dispute_txid)
    dummy_appointment_data = {"tx": penalty_tx.hex(), "tx_id": dispute_txid, "to_self_delay": 20}
    encrypted_blob = Cryptographer.encrypt(dummy_appointment_data.get("tx")[::-1], dummy_appointment_data.get("tx_id"))

    appointment_data = {
        "locator": locator,
        "to_self_delay": dummy_appointment_data.get("to_self_delay"),
        "encrypted_blob": encrypted_blob,
        "user_id": get_random_value_hex(16),
    }

    appointment = ExtendedAppointment.from_dict(appointment_data)
    api.watcher.locator_cache.cache[appointment.locator] = dispute_tx.tx_id.hex()
    appointment_signature = Cryptographer.sign(appointment.serialize(), user_sk)

    # Add the data to the cache
    api.watcher.locator_cache.cache[dispute_txid] = appointment.locator

    # The appointment should be accepted
    r = add_appointment(client, {"appointment": appointment.to_dict(), "signature": appointment_signature}, user_id)
    assert (
        r.status_code == HTTP_OK
        and r.json.get("available_slots") == 0
        and r.json.get("start_block") == api.watcher.last_known_block
    )
Example #5
0
def create_appointment(appointment_data):
    """
    Creates an appointment object from an appointment data dictionary provided by the user. Performs all the required
    sanity checks on the input data:

        - Check that the given commitment_txid is correct (proper format and not missing)
        - Check that the transaction is correct (not missing)

    Args:
        appointment_data (:obj:`dict`): a dictionary containing the appointment data.

    Returns:
        :obj:`common.appointment.Appointment`: An appointment built from the appointment data provided by the user.
    """

    tx_id = appointment_data.get("tx_id")
    tx = appointment_data.get("tx")

    if not tx_id:
        raise InvalidParameter("Missing tx_id, locator cannot be computed")
    elif not is_256b_hex_str(tx_id):
        raise InvalidParameter("Wrong tx_id, locator cannot be computed")
    elif not tx:
        raise InvalidParameter("The tx field is missing in the provided data")
    elif not isinstance(tx, str):
        raise InvalidParameter("The provided tx field is not a string")

    appointment_data["locator"] = compute_locator(tx_id)
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(tx, tx_id)

    return Appointment.from_dict(appointment_data)
def test_appointment_wrong_decryption_key(bitcoin_cli):
    # This tests an appointment encrypted with a key that has not been derived from the same source as the locator.
    # Therefore the tower won't be able to decrypt the blob once the appointment is triggered.
    commitment_tx, penalty_tx = create_txs(bitcoin_cli)

    # The appointment data is built using a random 32-byte value.
    appointment_data = build_appointment_data(get_random_value_hex(32), penalty_tx)

    # We cannot use teos_cli.add_appointment here since it computes the locator internally, so let's do it manually.
    # We will encrypt the blob using the random value and derive the locator from the commitment tx.
    appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"))
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(penalty_tx, get_random_value_hex(32))
    appointment = Appointment.from_dict(appointment_data)

    signature = Cryptographer.sign(appointment.serialize(), user_sk)
    data = {"appointment": appointment.to_dict(), "signature": signature}

    # Send appointment to the server.
    response = teos_cli.post_request(data, teos_add_appointment_endpoint)
    response_json = teos_cli.process_post_response(response)

    # Check that the server has accepted the appointment
    signature = response_json.get("signature")
    rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
    assert teos_id == Cryptographer.get_compressed_pk(rpk)
    assert response_json.get("locator") == appointment.locator

    # Trigger the appointment
    new_addr = bitcoin_cli.getnewaddress()
    broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)

    # The appointment should have been removed since the decryption failed.
    with pytest.raises(TowerResponseError):
        get_appointment_info(appointment.locator)
Example #7
0
def test_create_appointment():
    # Tests that an appointment is properly created provided the input data is correct
    appointment = teos_client.create_appointment(dummy_appointment_data)
    assert isinstance(appointment, Appointment)
    assert appointment.locator == dummy_appointment_data.get(
        "locator") and appointment.to_self_delay == dummy_appointment_data.get(
            "to_self_delay")
    assert appointment.encrypted_blob == Cryptographer.encrypt(
        dummy_appointment_data.get("tx"), dummy_appointment_data.get("tx_id"))
def test_check_breach(watcher):
    # A breach will be flagged as valid only if the encrypted blob can be properly decrypted and the resulting data
    # matches a transaction format.
    uuid = uuid4().hex
    appointment, dispute_tx = generate_dummy_appointment()
    dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")

    penalty_txid, penalty_rawtx = watcher.check_breach(uuid, appointment, dispute_txid)
    assert Cryptographer.encrypt(penalty_rawtx, dispute_txid) == appointment.encrypted_blob
def test_check_breach_invalid_transaction(watcher):
    # If the breach triggers an appointment with data that can be decrypted but does not match a transaction, it should
    # fail
    uuid = uuid4().hex
    appointment, dispute_tx = generate_dummy_appointment()
    dispute_txid = watcher.block_processor.decode_raw_transaction(dispute_tx).get("txid")

    # Set the blob to something "random"
    appointment.encrypted_blob = Cryptographer.encrypt(get_random_value_hex(200), dispute_txid)

    with pytest.raises(InvalidTransactionFormat):
        watcher.check_breach(uuid, appointment, dispute_txid)
Example #10
0
    def _generate_dummy_appointment():
        commitment_txid = get_random_value_hex(32)
        penalty_tx = get_random_value_hex(150)

        appointment_data = {
            "locator": compute_locator(commitment_txid),
            "to_self_delay": 20,
            "encrypted_blob": Cryptographer.encrypt(penalty_tx,
                                                    commitment_txid),
            "user_id": get_random_value_hex(16),
            "user_signature": get_random_value_hex(50),
            "start_block": 200,
        }

        return ExtendedAppointment.from_dict(appointment_data), commitment_txid
Example #11
0
def test_add_appointment_in_cache_invalid_blob(watcher):
    # Generate an appointment with an invalid transaction and add the dispute txid to the cache
    user_sk, user_pk = generate_keypair()
    user_id = Cryptographer.get_compressed_pk(user_pk)
    watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=1,
        subscription_expiry=watcher.block_processor.get_block_count() + 1)

    # We need to create the appointment manually
    commitment_tx, commitment_txid, penalty_tx = create_txs()

    locator = compute_locator(commitment_tx)
    dummy_appointment_data = {
        "tx": penalty_tx,
        "tx_id": commitment_txid,
        "to_self_delay": 20
    }
    encrypted_blob = Cryptographer.encrypt(penalty_tx[::-1], commitment_txid)

    appointment_data = {
        "locator": locator,
        "to_self_delay": dummy_appointment_data.get("to_self_delay"),
        "encrypted_blob": encrypted_blob,
        "user_id": get_random_value_hex(16),
    }

    appointment = Appointment.from_dict(appointment_data)
    watcher.locator_cache.cache[appointment.locator] = commitment_txid

    # Try to add the appointment
    user_signature = Cryptographer.sign(appointment.serialize(), user_sk)
    response = watcher.add_appointment(appointment, user_signature)
    appointment_receipt = receipts.create_appointment_receipt(
        user_signature, response.get("start_block"))

    # The appointment is accepted but dropped (same as an invalid appointment that gets triggered)
    assert (response and response.get("locator") == appointment.locator
            and Cryptographer.get_compressed_pk(watcher.signing_key.public_key)
            == Cryptographer.get_compressed_pk(
                Cryptographer.recover_pk(appointment_receipt,
                                         response.get("signature"))))

    assert not watcher.locator_uuid_map.get(appointment.locator)
    assert appointment.locator not in [
        tracker.get("locator")
        for tracker in watcher.responder.trackers.values()
    ]
Example #12
0
def test_appointment_wrong_key(bitcoin_cli, create_txs):
    # This tests an appointment encrypted with a key that has not been derived from the same source as the locator.
    # Therefore the tower won't be able to decrypt the blob once the appointment is triggered.
    commitment_tx, penalty_tx = create_txs

    # The appointment data is built using a random 32-byte value.
    appointment_data = build_appointment_data(bitcoin_cli, get_random_value_hex(32), penalty_tx)

    # We can't use teos_cli.add_appointment here since it computes the locator internally, so let's do it manually.
    # We will encrypt the blob using the random value and derive the locator from the commitment tx.
    appointment_data["locator"] = compute_locator(bitcoin_cli.decoderawtransaction(commitment_tx).get("txid"))
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(Blob(penalty_tx), get_random_value_hex(32))
    appointment = Appointment.from_dict(appointment_data)

    teos_pk, cli_sk, cli_pk_der = teos_cli.load_keys(
        cli_config.get("TEOS_PUBLIC_KEY"), cli_config.get("CLI_PRIVATE_KEY"), cli_config.get("CLI_PUBLIC_KEY")
    )
    hex_pk_der = binascii.hexlify(cli_pk_der)

    signature = Cryptographer.sign(appointment.serialize(), cli_sk)
    data = {"appointment": appointment.to_dict(), "signature": signature, "public_key": hex_pk_der.decode("utf-8")}

    # Send appointment to the server.
    response = teos_cli.post_appointment(data, teos_add_appointment_endpoint)
    response_json = teos_cli.process_post_appointment_response(response)

    # Check that the server has accepted the appointment
    signature = response_json.get("signature")
    assert signature is not None
    rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
    assert Cryptographer.verify_rpk(teos_pk, rpk) is True
    assert response_json.get("locator") == appointment.locator

    # Trigger the appointment
    new_addr = bitcoin_cli.getnewaddress()
    broadcast_transaction_and_mine_block(bitcoin_cli, commitment_tx, new_addr)

    # The appointment should have been removed since the decryption failed.
    sleep(1)
    appointment_info = get_appointment_info(appointment.locator)

    assert appointment_info is not None
    assert len(appointment_info) == 1
    assert appointment_info[0].get("status") == "not_found"
Example #13
0
def generate_dummy_appointment_data(real_height=True, start_time_offset=5, end_time_offset=30):
    if real_height:
        current_height = bitcoin_cli(bitcoind_connect_params).getblockcount()

    else:
        current_height = 10

    dispute_tx = create_dummy_transaction()
    dispute_txid = dispute_tx.tx_id.hex()
    penalty_tx = create_dummy_transaction(dispute_txid)

    dummy_appointment_data = {
        "tx": penalty_tx.hex(),
        "tx_id": dispute_txid,
        "start_time": current_height + start_time_offset,
        "end_time": current_height + end_time_offset,
        "to_self_delay": 20,
    }

    # dummy keys for this test
    client_sk, client_pk = generate_keypair()
    client_pk_hex = client_pk.format().hex()

    locator = compute_locator(dispute_txid)
    blob = Blob(dummy_appointment_data.get("tx"))

    encrypted_blob = Cryptographer.encrypt(blob, dummy_appointment_data.get("tx_id"))

    appointment_data = {
        "locator": locator,
        "start_time": dummy_appointment_data.get("start_time"),
        "end_time": dummy_appointment_data.get("end_time"),
        "to_self_delay": dummy_appointment_data.get("to_self_delay"),
        "encrypted_blob": encrypted_blob,
    }

    signature = Cryptographer.sign(Appointment.from_dict(appointment_data).serialize(), client_sk)

    data = {"appointment": appointment_data, "signature": signature, "public_key": client_pk_hex}

    return data, dispute_tx.hex()
Example #14
0
def test_add_appointment_in_cache_invalid_transaction(internal_api, client,
                                                      block_processor):
    internal_api.watcher.gatekeeper.registered_users[user_id] = UserInfo(
        available_slots=1,
        subscription_expiry=block_processor.get_block_count() + 1)

    # We need to create the appointment manually
    commitment_tx, commitment_txid, penalty_tx = create_txs()

    locator = compute_locator(commitment_tx)
    dummy_appointment_data = {
        "tx": penalty_tx,
        "tx_id": commitment_txid,
        "to_self_delay": 20
    }
    encrypted_blob = Cryptographer.encrypt(penalty_tx[::-1], commitment_txid)

    appointment_data = {
        "locator": locator,
        "to_self_delay": dummy_appointment_data.get("to_self_delay"),
        "encrypted_blob": encrypted_blob,
    }

    appointment = Appointment.from_dict(appointment_data)
    internal_api.watcher.locator_cache.cache[
        appointment.locator] = commitment_txid
    appointment_signature = Cryptographer.sign(appointment.serialize(),
                                               user_sk)

    # Add the data to the cache
    internal_api.watcher.locator_cache.cache[
        commitment_txid] = appointment.locator

    # The appointment should be accepted
    r = add_appointment(client, {
        "appointment": appointment.to_dict(),
        "signature": appointment_signature
    }, user_id)
    assert (r.status_code == HTTP_OK and r.json.get("available_slots") == 0
            and r.json.get("start_block") == block_processor.get_block_count())
Example #15
0
def generate_dummy_appointment():
    dispute_tx = create_dummy_transaction()
    dispute_txid = dispute_tx.tx_id.hex()
    penalty_tx = create_dummy_transaction(dispute_txid)

    locator = compute_locator(dispute_txid)
    dummy_appointment_data = {
        "tx": penalty_tx.hex(),
        "tx_id": dispute_txid,
        "to_self_delay": 20
    }
    encrypted_blob = Cryptographer.encrypt(dummy_appointment_data.get("tx"),
                                           dummy_appointment_data.get("tx_id"))

    appointment_data = {
        "locator": locator,
        "to_self_delay": dummy_appointment_data.get("to_self_delay"),
        "encrypted_blob": encrypted_blob,
        "user_id": get_random_value_hex(16),
    }

    return ExtendedAppointment.from_dict(appointment_data), dispute_tx.hex()
Example #16
0
def add_appointment(appointment_data, user_sk, teos_id, teos_url):
    """
    Manages the add_appointment command.

    The life cycle of the function is as follows:
        - Check that the given commitment_txid is correct (proper format and not missing)
        - Check that the transaction is correct (not missing)
        - Create the appointment locator and encrypted blob from the commitment_txid and the penalty_tx
        - Sign the appointment
        - Send the appointment to the tower
        - Wait for the response
        - Check the tower's response and signature

    Args:
        appointment_data (:obj:`dict`): a dictionary containing the appointment data.
        user_sk (:obj:`PrivateKey`): the user's private key.
        teos_id (:obj:`str`): the tower's compressed public key.
        teos_url (:obj:`str`): the teos base url.

    Returns:
        :obj:`tuple`: A tuple (`:obj:Appointment <common.appointment.Appointment>`, :obj:`str`) containing the
        appointment and the tower's signature.

    Raises:
        :obj:`InvalidParameter <cli.exceptions.InvalidParameter>`: if `appointment_data` or any of its fields is
        invalid.
        :obj:`ValueError`: if the appointment cannot be signed.
        :obj:`ConnectionError`: if the client cannot connect to the tower.
        :obj:`TowerResponseError <cli.exceptions.TowerResponseError>`: if the tower responded with an error, or the
        response was invalid.
    """

    if not appointment_data:
        raise InvalidParameter("The provided appointment JSON is empty")

    tx_id = appointment_data.get("tx_id")
    tx = appointment_data.get("tx")

    if not is_256b_hex_str(tx_id):
        raise InvalidParameter("The provided locator is wrong or missing")

    if not tx:
        raise InvalidParameter("The provided data is missing the transaction")

    appointment_data["locator"] = compute_locator(tx_id)
    appointment_data["encrypted_blob"] = Cryptographer.encrypt(tx, tx_id)
    appointment = Appointment.from_dict(appointment_data)
    signature = Cryptographer.sign(appointment.serialize(), user_sk)

    data = {"appointment": appointment.to_dict(), "signature": signature}

    # Send appointment to the server.
    logger.info("Sending appointment to the Eye of Satoshi")
    add_appointment_endpoint = "{}/add_appointment".format(teos_url)
    response = process_post_response(
        post_request(data, add_appointment_endpoint))

    tower_signature = response.get("signature")
    # Check that the server signed the appointment as it should.
    if not tower_signature:
        raise TowerResponseError(
            "The response does not contain the signature of the appointment")

    rpk = Cryptographer.recover_pk(appointment.serialize(), tower_signature)
    if teos_id != Cryptographer.get_compressed_pk(rpk):
        raise TowerResponseError(
            "The returned appointment's signature is invalid")

    logger.info("Appointment accepted and signed by the Eye of Satoshi")
    logger.info("Remaining slots: {}".format(response.get("available_slots")))
    logger.info("Start block: {}".format(response.get("start_block")))

    return appointment, tower_signature
Example #17
0
def on_commitment_revocation(plugin, **kwargs):
    """
    Sends an appointment to all registered towers for every net commitment transaction.

    kwargs should contain the commitment identifier (commitment_txid) and the penalty transaction (penalty_tx)

    Args:
        plugin (:obj:`Plugin`): this plugin.
    """

    try:
        commitment_txid, penalty_tx = arg_parser.parse_add_appointment_arguments(kwargs)
        appointment = Appointment(
            locator=compute_locator(commitment_txid),
            encrypted_blob=Cryptographer.encrypt(penalty_tx, commitment_txid),
            to_self_delay=20,  # does not matter for now, any value 20-2^32-1 would do
        )
        signature = Cryptographer.sign(appointment.serialize(), plugin.wt_client.sk)

    except (InvalidParameter, EncryptionError, SignatureError) as e:
        plugin.log(str(e), level="warn")
        return {"result": "continue"}

    # Send appointment to the towers.
    # FIXME: sending the appointment to all registered towers atm. Some management would be nice.
    for tower_id, tower in plugin.wt_client.towers.items():
        tower_update = {}

        if tower.status == "misbehaving":
            continue

        try:
            if tower.status == "reachable":
                tower_signature, available_slots = add_appointment(
                    plugin, tower_id, tower, appointment.to_dict(), signature
                )
                tower_update["appointment"] = (appointment.locator, tower_signature)
                tower_update["available_slots"] = available_slots

            else:
                if tower.status in ["temporarily unreachable", "unreachable"]:
                    plugin.log(f"{tower_id} is {tower.status}. Adding {appointment.locator} to pending")
                elif tower.status == "subscription error":
                    plugin.log(f"There is a subscription issue with {tower_id}. Adding appointment to pending")

                tower_update["pending_appointment"] = (appointment.to_dict(), signature), "add"

        except SignatureError as e:
            tower_update["status"] = "misbehaving"
            tower_update["misbehaving_proof"] = {
                "appointment": appointment.to_dict(),
                "signature": e.kwargs.get("signature"),
                "recovered_id": e.kwargs.get("recovered_id"),
                "receipt": e.kwargs.get("receipt"),
            }

        except TowerConnectionError:
            # All TowerConnectionError are transitory. Connections are tried on register so URLs cannot be malformed.
            # Flag appointment for retry
            tower_update["status"] = "temporarily unreachable"
            plugin.log(f"Adding {appointment.locator} to pending")
            tower_update["pending_appointment"] = (appointment.to_dict(), signature), "add"
            tower_update["retry"] = True

        except TowerResponseError as e:
            tower_update["status"] = e.kwargs.get("status")

            if tower_update["status"] in ["temporarily unreachable", "subscription error"]:
                plugin.log(f"Adding {appointment.locator} to pending")
                tower_update["pending_appointment"] = (appointment.to_dict(), signature), "add"

                if tower_update["status"] == "temporarily unreachable":
                    tower_update["retry"] = True

            if e.kwargs.get("invalid_appointment"):
                tower_update["invalid_appointment"] = (appointment.to_dict(), signature)

        finally:
            # Update memory and TowersDB
            plugin.wt_client.update_tower_state(tower_id, tower_update)

            if tower_update.get("retry"):
                plugin.wt_client.retrier.temp_unreachable_towers.put(tower_id)

    return {"result": "continue"}
Example #18
0
dummy_appointment_request = {
    "tx": get_random_value_hex(192),
    "tx_id": get_random_value_hex(32),
    "start_time": 1500,
    "end_time": 50000,
    "to_self_delay": 200,
}

# This is the format appointment turns into once it hits "add_appointment"
dummy_appointment_full = {
    "locator": compute_locator(dummy_appointment_request.get("tx_id")),
    "start_time": dummy_appointment_request.get("start_time"),
    "end_time": dummy_appointment_request.get("end_time"),
    "to_self_delay": dummy_appointment_request.get("to_self_delay"),
    "encrypted_blob": Cryptographer.encrypt(
        Blob(dummy_appointment_request.get("tx")), dummy_appointment_request.get("tx_id")
    ),
}

dummy_appointment = Appointment.from_dict(dummy_appointment_full)


def load_dummy_keys(*args):
    return dummy_pk, dummy_sk, dummy_pk.format(compressed=True)


def get_dummy_signature(*args):
    return Cryptographer.sign(dummy_appointment.serialize(), dummy_sk)


def get_bad_signature(*args):
Example #19
0
def test_encrypt():
    blob = Blob(data)

    assert Cryptographer.encrypt(blob, key) == encrypted_data
Example #20
0
get_subscription_info_endpoint = "{}/get_subscription_info".format(teos_url)

dummy_appointment_data = {
    "tx": get_random_value_hex(192),
    "tx_id": get_random_value_hex(32),
    "to_self_delay": 200
}

# This is the format appointment turns into once it hits "add_appointment"
dummy_appointment_dict = {
    "locator":
    compute_locator(dummy_appointment_data.get("tx_id")),
    "to_self_delay":
    dummy_appointment_data.get("to_self_delay"),
    "encrypted_blob":
    Cryptographer.encrypt(dummy_appointment_data.get("tx"),
                          dummy_appointment_data.get("tx_id")),
}
dummy_appointment = Appointment.from_dict(dummy_appointment_dict)

dummy_user_data = {
    "appointments": [],
    "available_slots": 100,
    "subscription_expiry": 7000
}

# The height is never checked in the tests, so we can make it up
CURRENT_HEIGHT = 300


@pytest.fixture
def keyfiles():
def test_encrypt():
    assert Cryptographer.encrypt(data, key) == encrypted_data
def test_encrypt_wrong_key_size():
    blob = get_random_value_hex(64)
    key = get_random_value_hex(31)

    with pytest.raises(InvalidParameter, match="32-byte hex value"):
        Cryptographer.encrypt(blob, key)
def test_encrypt_odd_length_data():
    blob = get_random_value_hex(64)[-1]
    key = get_random_value_hex(32)

    with pytest.raises(InvalidParameter, match="Odd-length"):
        Cryptographer.encrypt(blob, key)
Example #24
0
def add_appointment(args, teos_url, config):
    """
    Manages the add_appointment command, from argument parsing, trough sending the appointment to the tower, until
    saving the appointment receipt.

    The life cycle of the function is as follows:
        - Load the add_appointment arguments
        - Check that the given commitment_txid is correct (proper format and not missing)
        - Check that the transaction is correct (not missing)
        - Create the appointment locator and encrypted blob from the commitment_txid and the penalty_tx
        - Load the client private key and sign the appointment
        - Send the appointment to the tower
        - Wait for the response
        - Check the tower's response and signature
        - Store the receipt (appointment + signature) on disk

    If any of the above-mentioned steps fails, the method returns false, otherwise it returns true.

    Args:
        args (:obj:`list`): a list of arguments to pass to ``parse_add_appointment_args``. Must contain a json encoded
            appointment, or the file option and the path to a file containing a json encoded appointment.
        teos_url (:obj:`str`): the teos base url.
        config (:obj:`dict`): a config dictionary following the format of :func:`create_config_dict <common.config_loader.ConfigLoader.create_config_dict>`.

    Returns:
        :obj:`bool`: True if the appointment is accepted by the tower and the receipt is properly stored, false if any
        error occurs during the process.
    """

    # Currently the base_url is the same as the add_appointment_endpoint
    add_appointment_endpoint = teos_url

    teos_pk, cli_sk, cli_pk_der = load_keys(config.get("TEOS_PUBLIC_KEY"),
                                            config.get("CLI_PRIVATE_KEY"),
                                            config.get("CLI_PUBLIC_KEY"))

    try:
        hex_pk_der = binascii.hexlify(cli_pk_der)

    except binascii.Error as e:
        logger.error("Could not successfully encode public key as hex",
                     error=str(e))
        return False

    if teos_pk is None:
        return False

    # Get appointment data from user.
    appointment_data = parse_add_appointment_args(args)

    if appointment_data is None:
        logger.error("The provided appointment JSON is empty")
        return False

    valid_txid = check_sha256_hex_format(appointment_data.get("tx_id"))

    if not valid_txid:
        logger.error("The provided txid is not valid")
        return False

    tx_id = appointment_data.get("tx_id")
    tx = appointment_data.get("tx")

    if None not in [tx_id, tx]:
        appointment_data["locator"] = compute_locator(tx_id)
        appointment_data["encrypted_blob"] = Cryptographer.encrypt(
            Blob(tx), tx_id)

    else:
        logger.error("Appointment data is missing some fields")
        return False

    appointment = Appointment.from_dict(appointment_data)
    signature = Cryptographer.sign(appointment.serialize(), cli_sk)

    if not (appointment and signature):
        return False

    data = {
        "appointment": appointment.to_dict(),
        "signature": signature,
        "public_key": hex_pk_der.decode("utf-8")
    }

    # Send appointment to the server.
    server_response = post_appointment(data, add_appointment_endpoint)
    if server_response is None:
        return False

    response_json = process_post_appointment_response(server_response)

    if response_json is None:
        return False

    signature = response_json.get("signature")
    # Check that the server signed the appointment as it should.
    if signature is None:
        logger.error(
            "The response does not contain the signature of the appointment")
        return False

    rpk = Cryptographer.recover_pk(appointment.serialize(), signature)
    if not Cryptographer.verify_rpk(teos_pk, rpk):
        logger.error("The returned appointment's signature is invalid")
        return False

    logger.info("Appointment accepted and signed by the Eye of Satoshi")

    # All good, store appointment and signature
    return save_appointment_receipt(appointment.to_dict(), signature, config)