Watch
1
0
Fork
You've already forked mautrix-signal
0
mirror of https://github.com/mautrix/signal.git synced 2026-09-17 16:17:01 -04:00
mautrix-signal/pkg/signalmeow/protobuf/org/signal/chat/profile.proto
2026-09-05 00:57:28 +03:00

406 lines
16 KiB
Protocol Buffer
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* 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"];
}
}