def test_is_256b_hex_str(): # Only 32-byte hex encoded strings should pass the test wrong_inputs = [ None, str(), 213, 46.67, dict(), "A" * 63, "C" * 65, bytes(), get_random_value_hex(31) ] for wtype in wrong_inputs: assert is_256b_hex_str(wtype) is False for v in range(100): assert is_256b_hex_str(get_random_value_hex(32)) is True
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 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
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)
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