Watch
1
0
Fork
You've already forked mautrix-signal
0
mirror of https://github.com/mautrix/signal.git synced 2026-09-18 00:27:01 -04:00
mautrix-signal/pkg/signalmeow/protobuf/org/signal/chat/profile.proto

406 lines
16 KiB
Protocol Buffer
Raw Permalink Normal View History

/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
syntax = "proto3";
option java_multiple_files = true;
package org.signal.chat.profile;
import "google/protobuf/empty.proto";
import "org/signal/chat/common.proto";
import "org/signal/chat/errors.proto";
import "org/signal/chat/require.proto";
import "org/signal/chat/tag.proto";
// Provides methods for working with profiles and profile-related data.
service Profile {
option (require.auth) = AUTH_ONLY_AUTHENTICATED;
// Sets profile data and, if needed, returns credentials used by clients to upload a v1 avatar.
rpc SetProfile(SetProfileRequest) returns (SetProfileResponse) {}
// Retrieves profile data. Callers with an unidentified access key for the account
// should use the version of this method in `ProfileAnonymous` instead.
rpc GetProfile(GetProfileRequest) returns (GetProfileResponse) {}
// Returns anonymous credentials that may be presented with avatar operations in ProfilesAnonymous
//
// Note: `Accounts.SetZkCredentialKey` is a pre-requisite for this RPC
rpc GetAvatarCredentials(GetAvatarCredentialsRequest) returns (GetAvatarCredentialsResponse) {}
}
// Provides methods for working with profiles and profile-related data using "unidentified access"
// credentials. Callers must not submit any self-identifying credentials
// when calling methods in this service and must instead present the targeted account's
// unidentified access key as an anonymous authentication mechanism. Callers
// without an unidentified access key should use the equivalent, authenticated
// methods in `Profile` instead.
service ProfileAnonymous {
option (require.auth) = AUTH_ONLY_ANONYMOUS;
// Retrieves profile data.
rpc GetProfile(GetProfileAnonymousRequest) returns (GetProfileAnonymousResponse) {}
// Retrieves a profile key credential.
rpc GetExpiringProfileKeyCredential(GetExpiringProfileKeyCredentialAnonymousRequest) returns (GetExpiringProfileKeyCredentialAnonymousResponse) {}
// Returns credentials to upload a v2 avatar. After uploading the avatar, the client
// must call SetProfile with the new avatar URL in the encrypted `data`.
//
// Because avatars are uploaded anonymously, they have an expiration equal to the
// idle account expiration. Clients must periodically (recommended: every 90 days)
// call ExtendAvatarTTL to extend the TTl.
//
// Note: any existing avatar associated with these credentials will be deleted immediately
rpc GetAvatarUploadForm(GetAvatarUploadFormRequest) returns (GetAvatarUploadFormResponse) {}
// Extends the TTL of the avatar currently associated with the requests avatar auth credential
rpc ExtendAvatarTTL(ExtendAvatarTTLRequest) returns (ExtendAvatarTTLResponse) {}
// Deletes the avatar currently associated with the requests avatar auth credential.
//
// Clients must also call SetProfile to remove the avatar from the encrypted `data`.
rpc DeleteAvatar(DeleteAvatarRequest) returns (DeleteAvatarResponse) {}
}
message SetProfileV1Request {
enum AvatarChange {
AVATAR_CHANGE_UNCHANGED = 0;
AVATAR_CHANGE_CLEAR = 1;
AVATAR_CHANGE_UPDATE = 2;
}
// The ciphertext of a name that users must set on the profile.
bytes name = 1 [(require.exactlySize) = 81, (require.exactlySize) = 285];
// An enum to indicate what change, if any, is made to the avatar with this request.
AvatarChange avatar_change = 2;
// The ciphertext of an emoji that users can set on their profile.
bytes about_emoji = 3 [(require.exactlySize) = 0, (require.exactlySize) = 60];
// The ciphertext of a description that users can set on their profile.
bytes about = 4 [(require.exactlySize) = 0, (require.exactlySize) = 156, (require.exactlySize) = 282, (require.exactlySize) = 540];
// The ciphertext of the phone-number sharing setting on the profile. 29-byte encrypted boolean.
bytes phone_number_sharing = 6 [(require.exactlySize) = 29];
}
message SetProfileRequest {
// The profile version. Required.
bytes version = 1 [(require.exactlySize) = 32];
// The ciphertext of a serialized Profile protobuf. Required.
// 937 max length = 909 plaintext serialization + 28 bytes encryption overhead
bytes data = 2 [(require.nonEmpty) = true, (require.size).max = 937];
// The SHA-256 hash of the Profile ciphertext being replaced.
// This is used an optimistic lock against concurrent changes.
//
// Optional, if this is the initial request to create a version.
bytes expected_current_data_hash = 3 [(require.exactlySize) = 0, (require.exactlySize) = 32];
// The current profile version being updated.
// This is used as an optimistic lock against concurrent changes.
//
// Optional, if there is no current profile version for the account.
bytes expected_current_version = 4 [(require.exactlySize) = 0, (require.exactlySize) = 32];
// The ciphertext of the MobileCoin wallet ID on the profile.
bytes payment_address = 5 [(require.exactlySize) = 0, (require.exactlySize) = 582];
// A list of badge IDs associated with the profile.
repeated string badge_ids = 6;
// The profile key commitment. Used to issue a profile key credential response.
//
// Required during the v1 -> v2 migration period. Afterwards will be optional, if this is an update to an existing version.
bytes commitment = 7 [(require.exactlySize) = 0, (require.exactlySize) = 97];
// An embedded v1 request. Required during the v1 -> v2 migration period.
//
// Because this is a temporary field during the migration, it has the highest
// field number without serialization overhead. This is purely aesthetic.
SetProfileV1Request v1Request = 15 [(require.present) = true];
// next: 8
}
// Indicates that the account is not permitted to set a payment address,
// due to a disallowed country prefix on the account's phone number.
message PaymentsForbiddenInRegion {}
// Indicates that the account does not have the Profiles v2 capability, which
// is required to call Profiles.SetProfile
message ProfilesV2CapabilityRequired {}
message SetProfileResult {
// If the request included a v1 avatar change, this field contains the policy
// and credential used by clients to upload an avatar to the CDN.
//
// Because this is a temporary field during the migration, it has the highest
// field number without serialization overhead. This is purely aesthetic.
optional common.S3UploadForm v1_avatar_upload_form = 15;
// next: 1
}
message SetProfileResponse {
oneof response {
SetProfileResult result = 1;
// The current data hash did not match the request's expectation, indicating
// another device on the account may have written an update the caller does not know about.
errors.FailedPrecondition expected_data_write_conflict = 2 [(tag.reason) = "expected_data_write_conflict"];
// Payments are not permitted in the account's region, based on its phone
// number or the caller's IP address if the account does not have a phone
// number. The request should be retried without `payment_address.
PaymentsForbiddenInRegion payments_forbidden_in_region = 3 [(tag.reason) = "payments_forbidden_in_region"];
// The current version did not match the request's expectation, indicating
// another device on the account may have created a new version the caller does not know about.
errors.FailedPrecondition expected_version_write_conflict = 4 [(tag.reason) = "expected_version_write_conflict"];
// Because this is a temporary field during the migration, it has the highest
// field number without serialization overhead. This is purely aesthetic.
ProfilesV2CapabilityRequired profiles_v2_capability_required = 15 [(tag.reason) = "profiles_v2_capability_required"];
// next: 4
}
}
message GetProfileRequest {
// The ACI of the account for which to get profile data.
common.ServiceIdentifier account_identifier = 1 [(require.present) = true, (require.identityType) = IDENTITY_TYPE_ACI];
// The profile version to retrieve.
bytes version = 2 [(require.exactlySize) = 32];
// The `etag` from the previous request for this Profile version. If
// unchanged, the response will omit `profile` and `etag_matched` will be
// `true`.
bytes etag = 3 [(require.exactlySize) = 0, (require.exactlySize) = 10];
}
message GetProfileAnonymousRequest {
// Contains the data necessary to request a profile.
GetProfileRequest request = 1 [(require.present) = true];
oneof authentication {
// The unidentified access key for the targeted account.
bytes unidentified_access_key = 2 [(require.exactlySize) = 16];
// A group send endorsement token for the targeted account.
bytes group_send_token = 3 [(require.nonEmpty) = true];
}
}
message AccountInfo {
// The account identity key of the targeted account.
bytes identity_key = 1;
// A checksum of the unidentified access key for the targeted account.
bytes unidentified_access_key_fingerprint = 2;
// Whether the account has enabled sealed sender from anyone.
bool unrestricted_unidentified_access = 3;
// A list of the badges ids associated with the account. Metadata to display
// badges may be obtained by cross-referencing badge ids with
// RemoteConfiguration.GetBadges.
repeated string badge_ids = 4;
}
message ProfileResult {
// The ciphertext of the requested version of the profile
bytes data = 1;
// The ciphertext of the MobileCoin wallet ID on the profile.
bytes payment_address = 2;
// Information about the targeted account
AccountInfo account_info = 3;
// An entity tag for the profile. This may be used in subsequent requests
// for this profile version to optimize bandwidth.
//
// Note that this hash will not match the expected_data_hash used on
// SetProfile for concurrency control.
//
// Clients may validate that the value has been correctly calculated by
// calculating a 10-byte truncated TupleHash256 as follows:
//
// ```
// TupleHash256(S = "ProfileETag/v1", L = 80 bits, tuple = [
// data,
// payment_address,
// account_info.identity_key,
// account_info.unidentified_access_key_fingerprint,
// account_info.unrestricted_unidentified_access ? 0x01 : 0x00,
// uint32BE(count(account_info.badge_ids)),
// utf8(id) for each id in sortLexAscendingByUtf8Bytes(account_info.badge_ids),
// ])
//```
//
// Absent/empty fields contribute an empty element (not zero bytes omitted):
// a missing payment_address is still a present, zero-length tuple element.
//
// This etag will always exclusively cover the documented fields. If fields
// are added in the future, a new etag field must be introduced.
bytes etag = 4;
}
message LegacyProfileResult {
// The ciphertext of the name on the profile.
bytes name = 1;
// The ciphertext of the description on the profile.
bytes about = 2;
// The ciphertext of the emoji on the profile.
bytes about_emoji = 3;
// The cdn0 path of the avatar on the profile.
string avatar = 4;
// The ciphertext of the phone-number sharing setting on the profile.
bytes phone_number_sharing = 5;
// The ciphertext of the MobileCoin wallet ID on the profile.
bytes payment_address = 6;
// Information about the targeted account
AccountInfo account_info = 7;
}
message GetProfileResponse {
oneof response {
// The full profile data. The current profile did not match the provided
// etag (or no etag was provided).
ProfileResult profile = 1;
// The current profile matched the provided etag. If present, this will always be true.
bool etag_matched = 2 [(tag.reason) = "etag_match"];
errors.NotFound not_found = 3 [(tag.reason) = "not_found"];
// Will be present if there is no v2 Profile data for this version.
//
// Because this is a temporary field during the migration, it has the highest
// field number without serialization overhead. This is purely aesthetic.
LegacyProfileResult legacy_profile = 15 [(tag.reason) = "legacy_profile"];
}
// next: 4
}
message GetProfileAnonymousResponse {
oneof response {
// The full profile data. The current profile did not match the provided
// etag (or no etag was provided).
ProfileResult profile = 1;
// The current profile matched the provided etag. If present, this will always be true.
bool etag_matched = 2 [(tag.reason) = "etag_match"];
errors.NotFound not_found = 3 [(tag.reason) = "not_found"];
errors.FailedUnidentifiedAuthorization failed_unidentified_authorization = 4 [(tag.reason) = "failed_unidentified_authorization"];
// Will be present if there is no v2 Profile data for this version.
//
// Because this is a temporary field during the migration, it has the highest
// field number without serialization overhead. This is purely aesthetic.
LegacyProfileResult profile_v1 = 15 [(tag.reason) = "profile_v1"];
}
// next: 5
}
message GetExpiringProfileKeyCredentialRequest {
// The ACI of the account for which to get a profile key credential.
common.ServiceIdentifier account_identifier = 1 [(require.present) = true, (require.identityType) = IDENTITY_TYPE_ACI];
// A zkgroup request for a profile key credential.
bytes credential_request = 2 [(require.nonEmpty) = true];
// The type of credential being requested.
CredentialType credential_type = 3 [(require.specified) = true];
// The profile version for which to generate a profile key credential.
bytes version = 4 [(require.exactlySize) = 32];
}
message GetExpiringProfileKeyCredentialAnonymousRequest {
// Contains the data necessary to request an expiring profile key credential.
GetExpiringProfileKeyCredentialRequest request = 1 [(require.present) = true];
// The unidentified access key for the targeted account.
bytes unidentified_access_key = 2 [(require.exactlySize) = 16];
}
message GetExpiringProfileKeyCredentialResult {
// A zkgroup credential used by a client to prove that it has the profile key
// of a targeted account.
bytes profile_key_credential = 1;
}
message GetExpiringProfileKeyCredentialAnonymousResponse {
oneof response {
GetExpiringProfileKeyCredentialResult result = 1;
errors.NotFound not_found = 2 [(tag.reason) = "not_found"];
errors.FailedUnidentifiedAuthorization failed_unidentified_authorization = 3 [(tag.reason) = "failed_unidentified_authorization"];
}
}
enum CredentialType {
CREDENTIAL_TYPE_UNSPECIFIED = 0;
CREDENTIAL_TYPE_EXPIRING_PROFILE_KEY = 1;
}
// avatar auth
message GetAvatarCredentialsRequest {
bytes avatar_credentials_request = 1;
}
message GetAvatarCredentialsResponse {
oneof response {
bytes avatar_credentials = 1;
// the client must call Accounts.SetZkCredentialKey to call this method
errors.FailedPrecondition missing_zk_credential_key = 2 [(tag.reason) = "missing_zk_credential_key"];
}
}
message GetAvatarUploadFormRequest {
bytes avatar_credentials_presentation = 1;
// The length of the attachment for the requested upload form. Uploads
// performed with this form will be limited to the provided length.
uint32 upload_length = 2 [(require.range) = {min: 1, max: 10485760 /* 10 MiB */}];
}
message GetAvatarUploadFormResponse {
oneof response {
common.S3UploadForm avatar_upload_form = 1;
errors.FailedZkAuthentication invalid_credentials_presentation = 2 [(tag.reason) = "invalid_credentials_presentation"];
}
}
message ExtendAvatarTTLRequest {
bytes avatar_credentials_presentation = 1;
}
message ExtendAvatarTTLResponse {
oneof response {
// The avatar that was extended. May be used as a check against state de-synchronization.
string path = 1;
errors.FailedZkAuthentication invalid_credentials_presentation = 2 [(tag.reason) = "invalid_credentials_presentation"];
// the identity does not have an active avatar
errors.NotFound not_found = 3 [(tag.reason) = "no_active_avatar"];
}
}
message DeleteAvatarRequest {
bytes avatar_credentials_presentation = 1;
}
message DeleteAvatarResponse {
oneof response {
google.protobuf.Empty success = 1;
errors.FailedZkAuthentication invalid_credentials_presentation = 2 [(tag.reason) = "invalid_credentials_presentation"];
}
}