@Override
  public Mono<ConfirmUsernameHashResponse> confirmUsernameHash(final ConfirmUsernameHashRequest request) {
    final AuthenticatedDevice authenticatedDevice = AuthenticationUtil.requireAuthenticatedDevice();

    if (request.getUsernameHash().isEmpty()) {
      throw Status.INVALID_ARGUMENT
          .withDescription("Username hash must not be empty")
          .asRuntimeException();
    }

    if (request.getUsernameHash().size() != AccountController.USERNAME_HASH_LENGTH) {
      throw Status.INVALID_ARGUMENT
          .withDescription(String.format("Username hash length must be %d bytes, but was actually %d",
              AccountController.USERNAME_HASH_LENGTH, request.getUsernameHash().size()))
          .asRuntimeException();
    }

    if (request.getZkProof().isEmpty()) {
      throw Status.INVALID_ARGUMENT
          .withDescription("Zero-knowledge proof must not be empty")
          .asRuntimeException();
    }

    if (request.getUsernameCiphertext().isEmpty()) {
      throw Status.INVALID_ARGUMENT
          .withDescription("Username ciphertext must not be empty")
          .asRuntimeException();
    }

    if (request.getUsernameCiphertext().size() > AccountController.MAXIMUM_USERNAME_CIPHERTEXT_LENGTH) {
      throw Status.INVALID_ARGUMENT
          .withDescription(String.format("Username hash length must at most %d bytes, but was actually %d",
              AccountController.MAXIMUM_USERNAME_CIPHERTEXT_LENGTH, request.getUsernameCiphertext().size()))
          .asRuntimeException();
    }

    try {
      usernameHashZkProofVerifier.verifyProof(request.getZkProof().toByteArray(), request.getUsernameHash().toByteArray());
    } catch (final BaseUsernameException e) {
      throw Status.INVALID_ARGUMENT.withDescription("Could not verify proof").asRuntimeException();
    }

    return rateLimiters.getUsernameSetLimiter().validateReactive(authenticatedDevice.accountIdentifier())
        .then(Mono.fromFuture(() -> accountsManager.getByAccountIdentifierAsync(authenticatedDevice.accountIdentifier())))
        .map(maybeAccount -> maybeAccount.orElseThrow(Status.UNAUTHENTICATED::asRuntimeException))
        .flatMap(account -> Mono.fromFuture(() -> accountsManager.confirmReservedUsernameHash(account, request.getUsernameHash().toByteArray(), request.getUsernameCiphertext().toByteArray())))
        .map(updatedAccount -> ConfirmUsernameHashResponse.newBuilder()
            .setUsernameHash(ByteString.copyFrom(updatedAccount.getUsernameHash().orElseThrow()))
            .setUsernameLinkHandle(UUIDUtil.toByteString(updatedAccount.getUsernameLinkHandle()))
            .build())
        .onErrorMap(UsernameReservationNotFoundException.class, throwable -> Status.FAILED_PRECONDITION.asRuntimeException())
        .onErrorMap(UsernameHashNotAvailableException.class, throwable -> Status.NOT_FOUND.asRuntimeException());
  }
@Test
  void confirmUsernameHash() {
    final byte[] usernameHash = TestRandomUtil.nextBytes(AccountController.USERNAME_HASH_LENGTH);

    final byte[] usernameCiphertext = TestRandomUtil.nextBytes(32);

    final byte[] zkProof = TestRandomUtil.nextBytes(32);

    final UUID linkHandle = UUID.randomUUID();

    final Account account = mock(Account.class);

    when(accountsManager.getByAccountIdentifierAsync(AUTHENTICATED_ACI))
        .thenReturn(CompletableFuture.completedFuture(Optional.of(account)));

    when(accountsManager.confirmReservedUsernameHash(account, usernameHash, usernameCiphertext))
        .thenAnswer(invocation -> {
          final Account updatedAccount = mock(Account.class);

          when(updatedAccount.getUsernameHash()).thenReturn(Optional.of(usernameHash));
          when(updatedAccount.getUsernameLinkHandle()).thenReturn(linkHandle);

          return CompletableFuture.completedFuture(updatedAccount);
        });

    final ConfirmUsernameHashResponse expectedResponse = ConfirmUsernameHashResponse.newBuilder()
        .setUsernameHash(ByteString.copyFrom(usernameHash))
        .setUsernameLinkHandle(UUIDUtil.toByteString(linkHandle))
        .build();

    assertEquals(expectedResponse,
        authenticatedServiceStub().confirmUsernameHash(ConfirmUsernameHashRequest.newBuilder()
            .setUsernameHash(ByteString.copyFrom(usernameHash))
            .setUsernameCiphertext(ByteString.copyFrom(usernameCiphertext))
            .setZkProof(ByteString.copyFrom(zkProof))
            .build()));
  }