Beispiel #1
0
def test_is_compressed_pk():
    wrong_values = [
        None,
        3,
        15.23,
        "",
        {},
        (),
        object,
        str,
        get_random_value_hex(32),
        get_random_value_hex(34),
        "06" + get_random_value_hex(32),
    ]

    # check_user_pk must only accept values that is not a 33-byte hex string
    for i in range(100):
        if i % 2:
            prefix = "02"
        else:
            prefix = "03"
        assert is_compressed_pk(prefix + get_random_value_hex(32))

    # check_user_pk must only accept values that is not a 33-byte hex string
    for value in wrong_values:
        assert not is_compressed_pk(value)
Beispiel #2
0
def create_registration_receipt(user_id, available_slots, subscription_expiry):
    """
    Creates a registration receipt.

    The receipt has the following format:

        ``user_id (33-byte) | available_slots (4-byte) | subscription_expiry (4-byte)``

    All values are big endian.

    Args:
        user_id(:obj:`str`): the public key that identifies the user (33-bytes hex str).
        available_slots (:obj:`int`): the number of slots assigned to a user subscription (4-byte unsigned int).
        subscription_expiry (:obj:`int`): the expiry assigned to a user subscription (4-byte unsigned int).

    Returns:
          :obj:`bytes`: The serialized data to be signed.
    """

    if not is_compressed_pk(user_id):
        raise InvalidParameter(
            "Provided public key does not match expected format (33-byte hex string)"
        )
    elif not is_u4int(available_slots):
        raise InvalidParameter(
            "Provided available_slots must be a 4-byte unsigned integer")
    elif not is_u4int(subscription_expiry):
        raise InvalidParameter(
            "Provided subscription_expiry must be a 4-byte unsigned integer")

    return bytes.fromhex(user_id) + available_slots.to_bytes(
        4, "big") + subscription_expiry.to_bytes(4, "big")
Beispiel #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 and the subscription
            expiry (in absolute block height).

        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)")

        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
            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())

        return self.registered_users[user_id].available_slots, self.registered_users[user_id].subscription_expiry
Beispiel #4
0
def register(user_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_url (:obj:`str`): the teos base url.

    Returns:
        :obj:`dict`: a dictionary containing the tower response if the registration succeeded.

    Raises:
        :obj:`InvalidParameter <cli.exceptions.InvalidParameter>`: if `user_id` is invalid.
        :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 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))

    return response
    def store_tower_record(self, tower_id, tower_data):
        """
        Stores a tower record to the database. ``tower_id`` is used as identifier.

        Args:
            tower_id (:obj:`str`): a 33-byte hex-encoded string identifying the tower.
            tower_data (:obj:`dict`): the tower associated data, as a dictionary.

        Returns:
            :obj:`bool`: True if the tower record was stored in the database, False otherwise.
        """

        if is_compressed_pk(tower_id):
            try:
                self.create_entry(tower_id, json.dumps(tower_data.to_dict()))
                self.plugin.log(f"Adding tower to Tower's db (id={tower_id})")
                return True

            except (json.JSONDecodeError, TypeError):
                self.plugin.log(
                    f"Could't add tower to db. Wrong tower data format (tower_id={tower_id}, tower_data={tower_data.to_dict()})"
                )
                return False

        else:
            self.plugin.log(
                f"Could't add user to db. Wrong pk format (tower_id={tower_id}, tower_data={tower_data.to_dict()})"
            )
            return False
Beispiel #6
0
    def store_user(self, user_id, user_data):
        """
        Stores a user record to the database. ``user_pk`` is used as identifier.

        Args:
            user_id (:obj:`str`): a 33-byte hex-encoded string identifying the user.
            user_data (:obj:`dict`): the user associated data, as a dictionary.

        Returns:
            :obj:`bool`: True if the user was stored in the database, False otherwise.
        """

        if is_compressed_pk(user_id):
            try:
                self.create_entry(user_id, json.dumps(user_data))
                logger.info("Adding user to Gatekeeper's db", user_id=user_id)
                return True

            except json.JSONDecodeError:
                logger.info("Could't add user to db. Wrong user data format", user_id=user_id, user_data=user_data)
                return False

            except TypeError:
                logger.info("Could't add user to db", user_id=user_id, user_data=user_data)
                return False
        else:
            logger.info("Could't add user to db. Wrong pk format", user_id=user_id, user_data=user_data)
            return False
Beispiel #7
0
def parse_register_arguments(tower_id, host, port, config):
    """
    Parses the arguments of the register command and checks that they are correct.

    Args:
        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.
        host (:obj:`int`): the port to connect to, optional.
        config: (:obj:`dict`): the configuration dictionary.

    Returns:
        :obj:`tuple`: the tower id and tower network address.

    Raises:
        :obj:`common.exceptions.InvalidParameter`: if any of the parameters is wrong or missing.
    """

    if not isinstance(tower_id, str):
        raise InvalidParameter(
            f"tower id must be a compressed public key (33-byte hex value) not {str(tower_id)}"
        )

    # tower_id is of the form tower_id@[ip][:][port]
    if "@" in tower_id:
        if not (host and port):
            tower_id, tower_netaddr = tower_id.split("@")

            if not tower_netaddr:
                raise InvalidParameter("no tower endpoint was provided")

            # Only host was specified or colons where specified but not port
            if ":" not in tower_netaddr:
                tower_netaddr = f"{tower_netaddr}:{config.get('DEFAULT_PORT')}"
            elif tower_netaddr.endswith(":"):
                tower_netaddr = f"{tower_netaddr}{config.get('DEFAULT_PORT')}"

        else:
            raise InvalidParameter(
                "cannot specify host as both xxx@yyy and separate arguments")

    # host was specified, but no port, defaulting
    elif host:
        tower_netaddr = f"{host}:{config.get('DEFAULT_PORT')}"

    # host and port specified
    elif host and port:
        tower_netaddr = f"{host}:{port}"

    else:
        raise InvalidParameter("tower host is missing")

    if not is_compressed_pk(tower_id):
        raise InvalidParameter(
            "tower id must be a compressed public key (33-byte hex value)")

    return tower_id, tower_netaddr
    def get_user(self, user_id):
        """
        Gets information about a specific user.

        Args:
            user_id (:obj:`str`): the id of the requested user.

        Raises:
            :obj:`InvalidParameter`: if `user_id` is not in the valid format.
        """

        if not is_compressed_pk(user_id):
            raise InvalidParameter("Invalid user id")

        result = self.stub.get_user(GetUserRequest(user_id=user_id))
        return result.user
Beispiel #9
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
Beispiel #10
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_id does not match the expected format.
            :obj:`ConnectionRefusedError`: if bitcoind cannot be reached.
        """

        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():
            block_count = self.block_processor.get_block_count()
            if user_id not in self.registered_users:
                self.registered_users[user_id] = UserInfo(
                    self.subscription_slots, 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 = 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,
            )
Beispiel #11
0
def parse_get_appointment_arguments(tower_id, locator):
    """
    Parses the arguments of the get_appointment command and checks that they are correct.

    Args:
        tower_id (:obj:`str`): the identifier of the tower to connect to (a compressed public key).
        locator (:obj:`str`): the locator of the appointment to query the tower about.

    Returns:
        :obj:`tuple`: the tower id and appointment locator.

    Raises:
        :obj:`common.exceptions.InvalidParameter`: if any of the parameters is wrong or missing.
    """

    if not is_compressed_pk(tower_id):
        raise InvalidParameter(
            "tower id must be a compressed public key (33-byte hex value)")

    if not is_locator(locator):
        raise InvalidParameter("The provided locator is not valid",
                               locator=locator)

    return tower_id, locator
Beispiel #12
0
def main(command, args, config):
    setup_data_folder(config.get("DATA_DIR"))

    # Set the teos url
    teos_url = "{}:{}".format(config.get("API_CONNECT"),
                              config.get("API_PORT"))
    # If an http or https prefix if found, leaves the server as is. Otherwise defaults to http.
    if not teos_url.startswith("http"):
        teos_url = "http://" + teos_url

    try:
        if os.path.exists(config.get("USER_PRIVATE_KEY")):
            logger.debug("Client id found. Loading keys")
            user_sk, user_id = load_keys(config.get("USER_PRIVATE_KEY"))

        else:
            logger.info("Client id not found. Generating new keys")
            user_sk = Cryptographer.generate_key()
            Cryptographer.save_key_file(user_sk.to_der(), "user_sk",
                                        config.get("DATA_DIR"))
            user_id = Cryptographer.get_compressed_pk(user_sk.public_key)

        if command == "register":
            if not args:
                raise InvalidParameter(
                    "Cannot register. No tower id was given")
            else:
                teos_id = args.pop(0)
                if not is_compressed_pk(teos_id):
                    raise InvalidParameter(
                        "Cannot register. Tower id has invalid format")

                available_slots, subscription_expiry = register(
                    user_id, teos_id, teos_url)
                logger.info(
                    "Registration succeeded. Available slots: {}".format(
                        available_slots))
                logger.info("Subscription expires at block {}".format(
                    subscription_expiry))

                teos_id_file = os.path.join(config.get("DATA_DIR"), "teos_pk")
                Cryptographer.save_key_file(bytes.fromhex(teos_id),
                                            teos_id_file,
                                            config.get("DATA_DIR"))

        if command == "add_appointment":
            teos_id = load_teos_id(config.get("TEOS_PUBLIC_KEY"))
            appointment_data = parse_add_appointment_args(args)
            appointment = create_appointment(appointment_data)
            start_block, signature = add_appointment(appointment, user_sk,
                                                     teos_id, teos_url)
            save_appointment_receipt(appointment.to_dict(), start_block,
                                     signature,
                                     config.get("APPOINTMENTS_FOLDER_NAME"))

        elif command == "get_appointment":
            if not args:
                logger.error("No arguments were given")

            else:
                arg_opt = args.pop(0)

                if arg_opt in ["-h", "--help"]:
                    sys.exit(help_get_appointment())

                teos_id = load_teos_id(config.get("TEOS_PUBLIC_KEY"))
                appointment_data = get_appointment(arg_opt, user_sk, teos_id,
                                                   teos_url)
                if appointment_data:
                    logger.info(json.dumps(appointment_data, indent=4))

        elif command == "get_subscription_info":
            if args:
                arg_opt = args.pop(0)

                if arg_opt in ["-h", "--help"]:
                    sys.exit(help_get_subscription_info())

            teos_id = load_teos_id(config.get("TEOS_PUBLIC_KEY"))
            subscription_info = get_subscription_info(user_sk, teos_id,
                                                      teos_url)
            if subscription_info:
                logger.info(json.dumps(subscription_info, indent=4))

        elif command == "help":
            if args:
                command = args.pop(0)

                if command == "register":
                    sys.exit(help_register())

                if command == "add_appointment":
                    sys.exit(help_add_appointment())

                if command == "get_subscription_info":
                    sys.exit(help_get_subscription_info())

                elif command == "get_appointment":
                    sys.exit(help_get_appointment())

                else:
                    logger.error(
                        "Unknown command. Use help to check the list of available commands"
                    )

            else:
                sys.exit(show_usage())

    except (
            FileNotFoundError,
            IOError,
            ConnectionError,
            ValueError,
            BasicException,
    ) as e:
        logger.error(str(e))
    except Exception as e:
        logger.error("Unknown error occurred", error=str(e))
Beispiel #13
0
def test_load_teos_id(keyfiles):
    # Test that it correctly returns the teos id
    assert is_compressed_pk(
        teos_client.load_teos_id(keyfiles.public_key_file_path))