Example #1
0
    def register(self):
        user_id = request.get_json().get("public_key")

        available_slots = 100 if user_id not in self.users else self.users[
            user_id]["available_slots"] + 100
        subscription_expiry = CURRENT_HEIGHT + 4320
        self.users[user_id] = {
            "available_slots": available_slots,
            "subscription_expiry": subscription_expiry
        }
        registration_receipt = receipts.create_registration_receipt(
            user_id, available_slots, subscription_expiry)

        rcode = constants.HTTP_OK
        response = {
            "public_key":
            user_id,
            "available_slots":
            self.users[user_id].get("available_slots"),
            "subscription_expiry":
            self.users[user_id].get("subscription_expiry"),
            "subscription_signature":
            Cryptographer.sign(registration_receipt, self.sk),
        }

        return response, rcode
Example #2
0
def test_register(api, client, monkeypatch):
    # Tests registering a user within the tower

    # Monkeypatch the response from the InternalAPI so the user is accepted
    slots = config.get("SUBSCRIPTION_SLOTS")
    expiry = config.get("SUBSCRIPTION_DURATION")
    receipt = receipts.create_registration_receipt(user_id, slots, expiry)
    signature = Cryptographer.sign(receipt, teos_sk)
    response = RegisterResponse(
        user_id=user_id,
        available_slots=slots,
        subscription_expiry=expiry,
        subscription_signature=signature,
    )
    monkeypatch.setattr(api.stub, "register", lambda x: response)

    #  Send the register request
    data = {"public_key": user_id}
    r = client.post(register_endpoint, json=data)

    # Check the reply
    assert r.status_code == HTTP_OK
    assert r.json.get("public_key") == user_id
    assert r.json.get("available_slots") == config.get("SUBSCRIPTION_SLOTS")
    assert r.json.get("subscription_expiry") == config.get(
        "SUBSCRIPTION_DURATION")
    rpk = Cryptographer.recover_pk(receipt,
                                   r.json.get("subscription_signature"))
    assert Cryptographer.get_compressed_pk(rpk) == teos_id
Example #3
0
    def add_update_user(self, user_id):
        """
        Adds a new user or updates the subscription of an existing one, by adding additional slots.

        Args:
            user_id(:obj:`str`): the public key that identifies the user (33-bytes hex str).

        Returns:
            :obj:`tuple`: A tuple with the number of available slots in the user subscription, the subscription
            expiry (in absolute block height), and the registration_receipt.

        Raises:
            :obj:`InvalidParameter`: if the user_pk does not match the expected format.
        """

        if not is_compressed_pk(user_id):
            raise InvalidParameter(
                "Provided public key does not match expected format (33-byte hex string)"
            )

        with self.rw_lock.gen_wlock():
            if user_id not in self.registered_users:
                self.registered_users[user_id] = UserInfo(
                    self.subscription_slots,
                    self.block_processor.get_block_count() +
                    self.subscription_duration)
            else:
                # FIXME: For now new calls to register add subscription_slots to the current count and reset the expiry
                #  time
                if not is_u4int(
                        self.registered_users[user_id].available_slots +
                        self.subscription_slots):
                    raise InvalidParameter(
                        "Maximum slots reached for the subscription")

                self.registered_users[
                    user_id].available_slots += self.subscription_slots
                self.registered_users[user_id].subscription_expiry = (
                    self.block_processor.get_block_count() +
                    self.subscription_duration)

            self.user_db.store_user(user_id,
                                    self.registered_users[user_id].to_dict())
            receipt = create_registration_receipt(
                user_id,
                self.registered_users[user_id].available_slots,
                self.registered_users[user_id].subscription_expiry,
            )

            return (
                self.registered_users[user_id].available_slots,
                self.registered_users[user_id].subscription_expiry,
                receipt,
            )
Example #4
0
def test_create_registration_receipt_wrong_inputs():
    user_id = "02" + get_random_value_hex(32)
    available_slots = 100
    subscription_expiry = 4320

    wrong_user_ids = [
        "01" + get_random_value_hex(32), "04" + get_random_value_hex(31),
        "06" + get_random_value_hex(33)
    ]
    no_int = [{}, object, "", [], 3.4, None]
    overflow_iu4nt = pow(2, 32)

    for wrong_param in wrong_user_ids + no_int:
        with pytest.raises(InvalidParameter,
                           match="public key does not match expected format"):
            receipts.create_registration_receipt(wrong_param, available_slots,
                                                 subscription_expiry)
        with pytest.raises(
                InvalidParameter,
                match="available_slots must be a 4-byte unsigned integer"):
            receipts.create_registration_receipt(user_id, wrong_param,
                                                 subscription_expiry)
        with pytest.raises(
                InvalidParameter,
                match="subscription_expiry must be a 4-byte unsigned integer"):
            receipts.create_registration_receipt(user_id, available_slots,
                                                 wrong_param)

    # Same for overflow u4int
    with pytest.raises(
            InvalidParameter,
            match="available_slots must be a 4-byte unsigned integer"):
        receipts.create_registration_receipt(user_id, overflow_iu4nt,
                                             subscription_expiry)
    with pytest.raises(
            InvalidParameter,
            match="subscription_expiry must be a 4-byte unsigned integer"):
        receipts.create_registration_receipt(user_id, available_slots,
                                             overflow_iu4nt)
Example #5
0
def test_register():
    # Simulate a register response
    slots = 100
    expiry = CURRENT_HEIGHT + 4320
    signature = Cryptographer.sign(
        receipts.create_registration_receipt(dummy_user_id, slots, expiry),
        dummy_teos_sk)
    response = {
        "available_slots": slots,
        "subscription_expiry": expiry,
        "subscription_signature": signature
    }
    responses.add(responses.POST, register_endpoint, json=response, status=200)
    teos_client.register(dummy_user_id, dummy_teos_id, teos_url)
Example #6
0
def test_register_wrong_signature():
    # Simulate a register response with a wrong signature
    slots = 100
    expiry = CURRENT_HEIGHT + 4320
    signature = Cryptographer.sign(
        receipts.create_registration_receipt(dummy_user_id, slots, expiry),
        another_sk)
    response = {
        "available_slots": slots,
        "subscription_expiry": expiry,
        "subscription_signature": signature
    }
    responses.add(responses.POST, register_endpoint, json=response, status=200)

    with pytest.raises(TowerResponseError, match="signature is invalid"):
        teos_client.register(dummy_user_id, dummy_teos_id, teos_url)
Example #7
0
def test_create_registration_receipt():
    # Not much to test here, basically making sure the fields are in the correct order
    # The receipt format is user_id | available_slots | subscription_expiry

    user_id = "02" + get_random_value_hex(32)
    available_slots = 100
    subscription_expiry = 4320

    registration_receipt = receipts.create_registration_receipt(
        user_id, available_slots, subscription_expiry)

    assert registration_receipt[:33].hex() == user_id
    assert int.from_bytes(registration_receipt[33:37],
                          "big") == available_slots
    assert int.from_bytes(registration_receipt[37:],
                          "big") == subscription_expiry
Example #8
0
def register(user_id, teos_id, teos_url):
    """
    Registers the user to the tower.

    Args:
        user_id (:obj:`str`): a 33-byte hex-encoded compressed public key representing the user.
        teos_id (:obj:`str`): the tower's compressed public key.
        teos_url (:obj:`str`): the teos base url.

    Returns:
        :obj:`tuple`: A tuple containing the available slots count and the subscription expiry.

    Raises:
        :obj:`InvalidParameter`: if `user_id` is invalid.
        :obj:`ConnectionError`: if the client cannot connect to the tower.
        :obj:`TowerResponseError`: if the tower responded with an error, or the
            response was invalid.
    """

    if not is_compressed_pk(user_id):
        raise InvalidParameter("The cli public key is not valid")

    # Send request to the server.
    register_endpoint = "{}/register".format(teos_url)
    data = {"public_key": user_id}

    logger.info("Registering in the Eye of Satoshi")
    response = process_post_response(post_request(data, register_endpoint))

    available_slots = response.get("available_slots")
    subscription_expiry = response.get("subscription_expiry")
    tower_signature = response.get("subscription_signature")

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

    # Check that the signature is correct.
    subscription_receipt = receipts.create_registration_receipt(
        user_id, available_slots, subscription_expiry)
    rpk = Cryptographer.recover_pk(subscription_receipt, tower_signature)
    if teos_id != Cryptographer.get_compressed_pk(rpk):
        raise TowerResponseError(
            "The returned appointment's signature is invalid")

    return available_slots, subscription_expiry
Example #9
0
def test_register(internal_api, client):
    # Tests registering a user within the tower
    current_height = internal_api.watcher.block_processor.get_block_count()
    data = {"public_key": user_id}
    r = client.post(register_endpoint, json=data)
    assert r.status_code == HTTP_OK
    assert r.json.get("public_key") == user_id
    assert r.json.get("available_slots") == config.get("SUBSCRIPTION_SLOTS")
    assert r.json.get("subscription_expiry"
                      ) == current_height + config.get("SUBSCRIPTION_DURATION")

    slots = r.json.get("available_slots")
    expiry = r.json.get("subscription_expiry")
    subscription_receipt = receipts.create_registration_receipt(
        user_id, slots, expiry)
    rpk = Cryptographer.recover_pk(subscription_receipt,
                                   r.json.get("subscription_signature"))
    assert Cryptographer.get_compressed_pk(rpk) == teos_id
Example #10
0
def test_register_top_up(internal_api, client):
    # Calling register more than once will give us SUBSCRIPTION_SLOTS * number_of_calls slots.
    # It will also refresh the expiry.
    temp_sk, tmp_pk = generate_keypair()
    tmp_user_id = Cryptographer.get_compressed_pk(tmp_pk)
    current_height = internal_api.watcher.block_processor.get_block_count()

    data = {"public_key": tmp_user_id}

    for i in range(10):
        r = client.post(register_endpoint, json=data)
        slots = r.json.get("available_slots")
        expiry = r.json.get("subscription_expiry")
        assert r.status_code == HTTP_OK
        assert r.json.get("public_key") == tmp_user_id
        assert slots == config.get("SUBSCRIPTION_SLOTS") * (i + 1)
        assert expiry == current_height + config.get("SUBSCRIPTION_DURATION")

        subscription_receipt = receipts.create_registration_receipt(
            tmp_user_id, slots, expiry)
        rpk = Cryptographer.recover_pk(subscription_receipt,
                                       r.json.get("subscription_signature"))
        assert Cryptographer.get_compressed_pk(rpk) == teos_id
Example #11
0
def register(plugin, tower_id, host=None, port=None):
    """
    Registers the user to the tower.

    Args:
        plugin (:obj:`Plugin`): this plugin.
        tower_id (:obj:`str`): the identifier of the tower to connect to (a compressed public key).
        host (:obj:`str`): the ip or hostname to connect to, optional.
        port (:obj:`int`): the port to connect to, optional.

    Accepted tower_id formats:
        - tower_id@host:port
        - tower_id host port
        - tower_id@host (will default port to DEFAULT_PORT)
        - tower_id host (will default port to DEFAULT_PORT)

    Returns:
        :obj:`dict`: a dictionary containing the subscription data.
    """

    try:
        tower_id, tower_netaddr = arg_parser.parse_register_arguments(tower_id, host, port, plugin.wt_client.config)

        # Defaulting to http hosts for now
        if not tower_netaddr.startswith("http"):
            tower_netaddr = "http://" + tower_netaddr

        # Send request to the server.
        register_endpoint = f"{tower_netaddr}/register"
        data = {"public_key": plugin.wt_client.user_id}

        plugin.log(f"Registering in the Eye of Satoshi (tower_id={tower_id})")

        response = process_post_response(post_request(data, register_endpoint, tower_id))
        available_slots = response.get("available_slots")
        subscription_expiry = response.get("subscription_expiry")
        tower_signature = response.get("subscription_signature")

        if available_slots is None or not isinstance(available_slots, int):
            raise TowerResponseError(f"available_slots is missing or of wrong type ({available_slots})")
        if subscription_expiry is None or not isinstance(subscription_expiry, int):
            raise TowerResponseError(f"subscription_expiry is missing or of wrong type ({subscription_expiry})")
        if tower_signature is None or not isinstance(tower_signature, str):
            raise TowerResponseError(f"signature is missing or of wrong type ({tower_signature})")

        # Check tower signature
        registration_receipt = receipts.create_registration_receipt(
            plugin.wt_client.user_id, available_slots, subscription_expiry
        )
        Cryptographer.recover_pk(registration_receipt, tower_signature)

        plugin.log(f"Registration succeeded. Available slots: {available_slots}")

        # Save data
        tower_info = TowerInfo(tower_netaddr, available_slots)
        plugin.wt_client.lock.acquire()
        plugin.wt_client.towers[tower_id] = tower_info.get_summary()
        plugin.wt_client.db_manager.store_tower_record(tower_id, tower_info)
        plugin.wt_client.lock.release()

        return response

    except (InvalidParameter, TowerConnectionError, TowerResponseError, SignatureError) as e:
        plugin.log(str(e), level="warn")
        return e.to_json()