def test_init_blob(): data = get_random_value_hex(64) blob = Blob(data) assert isinstance(blob, Blob) # Wrong data try: Blob(unhexlify(get_random_value_hex(64))) assert False, "Able to create blob with wrong data" except ValueError: assert True
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_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
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"
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()
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)
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):
def test_encrypt(): blob = Blob(data) assert Cryptographer.encrypt(blob, key) == encrypted_data