syntax = "proto3";

package leak_detection;

option optimize_for = LITE_RUNTIME;

// See internal/identity/passwords/leak/check/v1/service.proto for the server
// side definition.
service PasswordsLeakCheckService {
  // Enables clients to privately check whether given username-password pairs
  // are part of a leak that we collected.
  //
  // The protocol used here requires the client to request buckets of known
  // leaks identified by a username-hash prefix (username_hash_prefix).
  // In response, the server returns all the leaks (hashed username-password
  // pairs) of the requested buckets (leak_match).
  //
  // In order to not leak the database of hashed username-password pairs
  // to the client, we added the blinders protocol (go/blinders) into the mix.
  // This allows the server to only return an encrypted version of the hashes
  // to the clients (encrypted_leak_hash). The clients will not know the key
  // with which the server encrypted the hashes.
  // In order for the client to figure out if their credentials actually got
  // leaked, they need to encrypt their username-password pair hashes with their
  // own secret key and send the encrypted result to the server
  // (encrypted_lookup_hash).
  // The server will then reencrypt encrypted_lookup_hash with their own secret
  // key and return that in the result (reencrypted_lookup_hash).
  // The client will then reverse their own encryption on
  // reencrypted_lookup_hash and look up if the result is in the list of
  // returned leaks (leak_match). If so, the client has identified a leak.
  rpc LookupLeaks(LookupLeaksRequest) returns (LookupLeaksResponse) {}
}

message LookupLeaksRequest {
  // The prefix of the username hash (algorithm: SHA-256;
  // string encoding: UTF8).
  //
  // The underlying username is expected to be canonicalized by lower-casing
  // and stripping a mail-address host in case the username is a mail address.
  //
  // Only a most significant bit prefix with the length of
  // username_hash_prefix_length bits is to be set, the rest needs to be filled
  // with 0.
  //
  // The client can request all known leaks for a given username bucket.
  // This specifies the requested username buckets. All returned
  // `leak_match` values will belong to one of the requested username
  // buckets.
  repeated bytes username_hash_prefix = 1;

  // The length of the username hash prefix in bits.
  // We only allow 24 bits at the moment.
  //
  // Note: the prefix length is limited in order to:
  // - Protect client against too low anonymity (upper limit)
  // - Protect server against full database download (lower limit)
  uint32 username_hash_prefix_length = 2;

  // The full hash of username and password which was encrypted using a
  // commutative cipher.
  //
  // The underlying username is expected to be canonicalized the same as
  // described for username_hash_prefix.
  //
  // The key should be randomly generated by the client. It can choose a new one
  // per request, or even per hash. The server can't decrypt this and
  // won't know the difference.
  repeated bytes encrypted_lookup_hash = 3;
}

message LookupLeaksResponse {
  // The leaks that match the requested `username_hash_prefix`.
  // This list is sorted by `encrypted_leak_hash` in order to enable binary
  // search for matches.
  repeated LeakMatch leak_match = 1;

  // The reencrypted `encrypted_lookup_hash` from the request. In order for the
  // clients to check for matches, it needs the server to reencrypt the
  // `encrypted_lookup_hash` that they sent. The client can then reverse their
  // own encryption and check `leak_match` for the resulting values.
  repeated ReencryptedLookupHash reencrypted_lookup_hash = 2;

  message LeakMatch {
    // The requested username_hash_prefix that the leak matches belong to.
    bytes username_hash_prefix = 2;

    // The full hash of username and password that was discovered in a leak
    // which was encrypted using a commutative cipher with a private key picked
    // by the server.
    // The hashing algorithm and cipher used are the same as defined in the
    // request.
    // Note: There are no guarantees that the server key stays the same between
    // subsequent requests.
    repeated bytes encrypted_leak_hash = 1;
  }

  message ReencryptedLookupHash {
    // The same as `encrypted_lookup_hash` from the request. This can be used by
    // the client to link the reencrypted hash to the originally requested
    // one.
    bytes encrypted_lookup_hash = 1;
    // The `encrypted_lookup_hash` reencrypted with the server's  private key
    // (the same key that is used for encrypting `encrypted_leak_hash`).
    // This enables the client to reverse their own encryption, giving the
    // client a server-side only encrypted leak entry which it can use to check
    // if `leak_match` contains it.
    bytes reencrypted_lookup_hash = 2;
  }
}
