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