Пример #1
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)
Пример #2
0
def create_appointment_receipt(user_signature, start_block):
    """
    Creates an appointment receipt.

    The receipt has the following format:

        ``user_signature | start_block (4-byte)``

    All values are big endian.

    Args:
        user_signature (:obj:`str`): the signature of the appointment by the user.
        start_block (:obj:`int`): the block height at which the tower will start watching for the appointment.

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

    if not isinstance(user_signature, str):
        raise InvalidParameter("Provided user_signature is invalid")
    elif not is_u4int(start_block):
        raise InvalidParameter(
            "Provided start_block must be a 4-byte unsigned integer")

    return pyzbase32.decode_bytes(user_signature) + struct.pack(
        ">I", start_block)
Пример #3
0
    def load_key_file(file_path):
        """
        Loads a key from a key file.

        Args:
            file_path (:obj:`str`): the path to the key file to be loaded.

        Returns:
            :obj:`bytes`: the key file data if the file can be found and read.

        Raises:
             :obj:`InvalidParameter`: if the file_path has wrong format or cannot be found.
             :obj:`InvalidKey`: if the key cannot be loaded from the file. It covers temporary I/O errors.
        """

        if not isinstance(file_path, str):
            raise InvalidParameter(
                "Key file path was expected, {} received".format(
                    type(file_path)))

        try:
            with open(file_path, "rb") as key_file:
                key = key_file.read()
            return key

        except FileNotFoundError:
            raise InvalidParameter(
                "Key file not found at {}. Please check your settings".format(
                    file_path))

        except IOError as e:
            raise InvalidKey("Key file cannot be loaded", exception=e)
Пример #4
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")
Пример #5
0
    def save_key_file(key, name, data_dir):
        """
        Saves a key to disk in DER format.

        Args:
            key (:obj:`bytes`): the key to be saved to disk.
            name (:obj:`str`): the name of the key file to be generated.
            data_dir (:obj:`str`): the data directory where the file will be saved.

        Raises:
            :obj:`InvalidParameter`: If the given key is not bytes or the name or data_dir are not strings.
        """

        if not isinstance(key, bytes):
            raise InvalidParameter("Key must be bytes, {} received".format(
                type(key)))

        if not isinstance(name, str):
            raise InvalidParameter("Key name must be str, {} received".format(
                type(name)))

        if not isinstance(data_dir, str):
            raise InvalidParameter("Data dir must be str, {} received".format(
                type(data_dir)))

        # Create the output folder it it does not exist (and all the parents if they don't either)
        Path(data_dir).mkdir(parents=True, exist_ok=True)

        with open(os.path.join(data_dir, "{}.der".format(name)),
                  "wb") as der_out:
            der_out.write(key)
Пример #6
0
def parse_add_appointment_arguments(kwargs):
    """
    Parses the arguments of the add_appointment command and checks that they are correct.

    The expected arguments are a commitment transaction id (32-byte hex string) and the penalty transaction.

    Args:
        kwargs (:obj:`dict`): a dictionary of arguments.

    Returns:
        :obj:`tuple`: the commitment transaction id and the penalty transaction.

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

    # Arguments to add_appointment come from c-lightning and they have been sanitised. Checking this just in case.
    commitment_txid = kwargs.get("commitment_txid")
    penalty_tx = kwargs.get("penalty_tx")

    if commitment_txid is None:
        raise InvalidParameter("missing required parameter: commitment_txid")

    if penalty_tx is None:
        raise InvalidParameter("missing required parameter: penalty_tx")

    if not is_256b_hex_str(commitment_txid):
        raise InvalidParameter("commitment_txid has invalid format")

    # Checking the basic stuff for the penalty transaction for now
    if type(penalty_tx) is not str or re.search(r"^[0-9A-Fa-f]+$",
                                                penalty_tx) is None:
        raise InvalidParameter("penalty_tx has invalid format")

    return commitment_txid, penalty_tx
Пример #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
Пример #8
0
    def run(rpc_client, opts_args):
        opts, args = opts_args

        if not args:
            raise InvalidParameter("No user_id was given")
        if len(args) > 1:
            raise InvalidParameter(
                f"Expected only one argument, not {len(args)}")

        return rpc_client.get_user(args[0])
Пример #9
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,
            )
Пример #10
0
def parse_add_appointment_args(args):
    """
    Parses the arguments of the add_appointment command.

    Args:
        args (:obj:`list`): a list of command line arguments that must contain a json encoded appointment, or the file
            option and the path to a file containing a json encoded appointment.

    Returns:
        :obj:`dict`: A dictionary containing the appointment data.

    Raises:
        :obj:`InvalidParameter`: if the appointment data is not JSON encoded.
        :obj:`FileNotFoundError`: if ``-f`` is passed and the appointment file is not found.
        :obj:`IOError`: if ``-f`` was passed and the file cannot be read.
    """

    use_help = "Use 'help add_appointment' for help of how to use the command"

    if not args:
        raise InvalidParameter("No appointment data provided. " + use_help)

    arg_opt = args.pop(0)

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

        if arg_opt in ["-f", "--file"]:
            fin = args.pop(0)
            if not os.path.isfile(fin):
                raise FileNotFoundError("Cannot find {}".format(fin))

            try:
                with open(fin) as f:
                    appointment_data = json.load(f)

            except IOError as e:
                raise IOError("Cannot read appointment file. {}".format(
                    str(e)))

        else:
            appointment_data = json.loads(arg_opt)

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

    except json.JSONDecodeError:
        raise InvalidParameter(
            "Non-JSON encoded data provided as appointment. " + use_help)

    return appointment_data
Пример #11
0
    def recover_pk(message, zb32_sig):
        """
        Recovers an ECDSA public key from a given message and zbase32 signature.

        Args:
            message(:obj:`bytes`): original message from where the signature was generated.
            zb32_sig(:obj:`str`): the zbase32 signature of the message.

        Returns:
           :obj:`PublicKey`: The public key if it can be recovered.

        Raises:
             :obj:`InvalidParameter`: if the message and/or signature have a wrong value.
             :obj:`SignatureError`: if a public key cannot be recovered from the given signature.
        """

        if not isinstance(message, bytes):
            raise InvalidParameter(
                "Wrong value passed as message. Received {}, expected (bytes)".
                format(type(message)))

        if not isinstance(zb32_sig, str):
            raise InvalidParameter(
                "Wrong value passed as zbase32_sig. Received {}, expected (str)"
                .format(type(zb32_sig)))

        sigrec = pyzbase32.decode_bytes(zb32_sig)

        try:
            rsig_recid = sigrec_decode(sigrec)
            pk = PublicKey.from_signature_and_message(rsig_recid,
                                                      LN_MESSAGE_PREFIX +
                                                      message,
                                                      hasher=sha256d)
            return pk

        except ValueError as e:
            # Several errors fit here: Signature length != 65, wrong recover id and failed to parse signature.
            # All of them return raise ValueError.
            raise SignatureError(
                "Cannot recover a public key from the given signature. " +
                str(e))

        except Exception as e:
            if "failed to recover ECDSA public key" in str(e):
                raise SignatureError(
                    "Cannot recover a public key from the given signature")
            else:
                raise SignatureError("Unknown exception. " + str(e))
Пример #12
0
    def get_compressed_pk(pk):
        """
        Computes a compressed, hex-encoded, public key given a ``PublicKey``.

        Args:
            pk(:obj:`PublicKey`): a given public key.

        Returns:
            :obj:`str`: A compressed, hex-encoded, public key (33-byte long) if it can be compressed.

        Raises:
             :obj:`InvalidParameter`: if the value passed as public key is not a PublicKey object.
             :obj:`InvalidKey`: if the public key has not been properly created.
        """

        if not isinstance(pk, PublicKey):
            raise InvalidParameter(
                "Wrong value passed as pk. Received {}, expected (PublicKey)".
                format(type(pk)))

        try:
            compressed_pk = pk.format(compressed=True)
            return hexlify(compressed_pk).decode("utf-8")

        except TypeError as e:
            raise InvalidKey("PublicKey has invalid initializer", error=str(e))
Пример #13
0
def get_appointment(plugin, tower_id, locator):
    """
    Gets information about an appointment from the tower.

    Args:
        plugin (:obj:`Plugin`): this plugin.
        tower_id (:obj:`str`): the identifier of the tower to query.
        locator (:obj:`str`): the appointment locator.

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

    # FIXME: All responses from the tower should be signed.
    try:
        tower_id, locator = arg_parser.parse_get_appointment_arguments(tower_id, locator)

        if tower_id not in plugin.wt_client.towers:
            raise InvalidParameter("tower_id is not within the registered towers", tower_id=tower_id)

        message = f"get appointment {locator}"
        signature = Cryptographer.sign(message.encode("utf-8"), plugin.wt_client.sk)
        data = {"locator": locator, "signature": signature}

        # Send request to the server.
        tower_netaddr = plugin.wt_client.towers[tower_id].netaddr
        get_appointment_endpoint = f"{tower_netaddr}/get_appointment"
        plugin.log(f"Requesting appointment from {tower_id}")

        response = process_post_response(post_request(data, get_appointment_endpoint, tower_id))
        return response

    except (InvalidParameter, TowerConnectionError, TowerResponseError) as e:
        plugin.log(str(e), level="warn")
        return e.to_json()
Пример #14
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
Пример #15
0
def get_appointment(locator, user_sk, teos_id, teos_url):
    """
    Gets information about an appointment from the tower.

    Args:
        locator (:obj:`str`): the appointment locator used to identify it.
        user_sk (:obj:`PrivateKey`): the user's private key.
        teos_id (:obj:`PublicKey`): the tower's compressed public key.
        teos_url (:obj:`str`): the teos base url.

    Returns:
        :obj:`dict`: A dictionary containing the appointment data.

    Raises:
        :obj:`InvalidParameter`: if `appointment_data` or any of its fields 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.
    """

    # FIXME: All responses from the tower should be signed. Not using teos_id atm.

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

    message = "get appointment {}".format(locator)
    signature = Cryptographer.sign(message.encode("utf-8"), user_sk)
    data = {"locator": locator, "signature": signature}

    # Send request to the server.
    get_appointment_endpoint = "{}/get_appointment".format(teos_url)
    logger.info("Requesting appointment from the Eye of Satoshi")
    response = process_post_response(post_request(data, get_appointment_endpoint))

    return response
Пример #16
0
    def check_data_key_format(data, secret):
        """
        Checks that the data and secret that will be used to by ``encrypt`` / ``decrypt`` are properly formatted.

        Args:
              data(:obj:`str`): the data to be encrypted.
              secret(:obj:`str`): the secret used to derive the encryption key.

        Raises:
              :obj:`InvalidParameter`: if either the ``key`` and/or ``data`` are not properly formatted.
        """

        if len(data) % 2:
            raise InvalidParameter("Incorrect (Odd-length) data", data=data)

        if not is_256b_hex_str(secret):
            raise InvalidParameter(
                "Secret must be a 32-byte hex value (64 hex chars)",
                secret=secret)
Пример #17
0
def get_request_data_json(request):
    """
    Gets the content of a json POST request and makes sure it decodes to a dictionary.

    Args:
        request (:obj:`Request`): the request sent by the user.

    Returns:
        :obj:`dict`: the dictionary parsed from the json request.

    Raises:
        :obj:`InvalidParameter`: if the request is not json encoded or it does not decodes to a dictionary.
    """

    if request.is_json:
        request_data = request.get_json()
        if isinstance(request_data, dict):
            return request_data
        else:
            raise InvalidParameter("Invalid request content")
    else:
        raise InvalidParameter("Request is not json encoded")
Пример #18
0
    def sign(message, sk):
        """
        Signs a given message with a given secret key using ECDSA over secp256k1.

        Args:
            message(:obj:`bytes`): the data to be signed.
            sk(:obj:`PrivateKey`): the ECDSA secret key to be used to sign the data.

        Returns:
           :obj:`str`: The zbase32 signature of the given message is it can be signed.

        Raises:
             :obj:`InvalidParameter`: if the message and/or secret key have a wrong value.
             :obj:`SignatureError`: if there is an error during the signing process.
        """

        if not isinstance(message, bytes):
            raise InvalidParameter(
                "Wrong value passed as message. Received {}, expected (bytes)".
                format(type(message)))

        if not isinstance(sk, PrivateKey):
            raise InvalidParameter(
                "Wrong value passed as sk. Received {}, expected (PrivateKey)".
                format(type(message)))

        try:
            rsig_rid = sk.sign_recoverable(LN_MESSAGE_PREFIX + message,
                                           hasher=sha256d)
            sigrec = sigrec_encode(rsig_rid)
            zb32_sig = pyzbase32.encode_bytes(sigrec).decode()

        except ValueError as e:
            raise SignatureError("Couldn't sign the message. " + str(e))

        return zb32_sig
Пример #19
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
Пример #20
0
    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
Пример #21
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
Пример #22
0
def raise_invalid_parameter(*args, **kwargs):
    # Message is passed in the API response
    raise InvalidParameter("Invalid parameter message")
Пример #23
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
Пример #24
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))