Add verified group title tracking and syncing.

This commit is contained in:
Cody Henthorne
2026-04-17 15:52:56 -04:00
committed by GitHub
parent f680256f1d
commit 76e30ab09f
16 changed files with 440 additions and 47 deletions

View File

@@ -113,7 +113,7 @@ class ConversationHeaderView : AbstractComposeView {
val isOfficialAccount = recipient.showVerified
val showUnverifiedName = if (recipient.isGroup) {
!groupInfo.hasExistingContacts && !(groupInfo.fullMemberCount == 1 && groupInfo.isMember)
!info.groupInfo.nameVerified
} else if (!isOfficialAccount) {
recipient.nickname.isEmpty && !recipient.isSystemContact
} else {

View File

@@ -74,6 +74,7 @@ import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentPoin
import org.whispersystems.signalservice.api.messages.SignalServiceAttachmentRemoteId
import org.whispersystems.signalservice.api.push.DistributionId
import java.io.Closeable
import java.security.MessageDigest
import java.security.SecureRandom
import java.time.Instant
import java.util.Optional
@@ -111,6 +112,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
const val SHOW_AS_STORY_STATE = "show_as_story_state"
const val LAST_FORCE_UPDATE_TIMESTAMP = "last_force_update_timestamp"
const val GROUP_SEND_ENDORSEMENTS_EXPIRATION = "group_send_endorsements_expiration"
const val V2_VERIFIED_NAME_HASH = "verified_name_hash"
/** 32 bytes serialized [GroupMasterKey] */
const val V2_MASTER_KEY = "master_key"
@@ -124,14 +126,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
@JvmField
val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
$ID INTEGER PRIMARY KEY,
$GROUP_ID TEXT NOT NULL UNIQUE,
$ID INTEGER PRIMARY KEY,
$GROUP_ID TEXT NOT NULL UNIQUE,
$RECIPIENT_ID INTEGER NOT NULL UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
$TITLE TEXT DEFAULT NULL,
$AVATAR_ID INTEGER DEFAULT 0,
$AVATAR_ID INTEGER DEFAULT 0,
$AVATAR_KEY BLOB DEFAULT NULL,
$AVATAR_CONTENT_TYPE TEXT DEFAULT NULL,
$AVATAR_DIGEST BLOB DEFAULT NULL,
$AVATAR_CONTENT_TYPE TEXT DEFAULT NULL,
$AVATAR_DIGEST BLOB DEFAULT NULL,
$TIMESTAMP INTEGER DEFAULT 0,
$IS_MEMBER INTEGER DEFAULT 1,
$MMS INTEGER DEFAULT 0,
@@ -144,7 +146,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
$SHOW_AS_STORY_STATE INTEGER DEFAULT ${ShowAsStoryState.IF_ACTIVE.code},
$LAST_FORCE_UPDATE_TIMESTAMP INTEGER DEFAULT 0,
$GROUP_SEND_ENDORSEMENTS_EXPIRATION INTEGER DEFAULT 0,
$TERMINATED_BY INTEGER DEFAULT 0
$TERMINATED_BY INTEGER DEFAULT 0,
$V2_VERIFIED_NAME_HASH BLOB DEFAULT NULL
)
"""
@@ -167,6 +170,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
V2_MASTER_KEY,
V2_REVISION,
V2_DECRYPTED_GROUP,
V2_VERIFIED_NAME_HASH,
LAST_FORCE_UPDATE_TIMESTAMP,
GROUP_SEND_ENDORSEMENTS_EXPIRATION
)
@@ -177,6 +181,11 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
.toList()
val CREATE_TABLES = arrayOf(CREATE_TABLE, MembershipTable.CREATE_TABLE)
@JvmStatic
fun computeVerifiedNameHash(title: String?): ByteArray? {
return title?.let { MessageDigest.getInstance("SHA-256").digest(it.toByteArray(Charsets.UTF_8)) }
}
}
class MembershipTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
@@ -544,6 +553,14 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
.run()
}
fun setVerifiedGroupNameHash(groupId: GroupId.V2, verifiedNameHash: ByteArray?) {
writableDatabase
.update(TABLE_NAME)
.values(V2_VERIFIED_NAME_HASH to verifiedNameHash)
.where("$GROUP_ID = ?", groupId)
.run()
}
@WorkerThread
fun getGroupMemberIds(groupId: GroupId, memberSet: MemberSet): List<RecipientId> {
return if (groupId.isV2) {
@@ -607,19 +624,25 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
throw LegacyGroupInsertException(groupId)
}
return create(groupId, title, members, avatar, null, null, null)
return create(groupId, title, members, avatar, null, null, null, null)
}
@CheckReturnValue
fun create(groupId: GroupId.Mms, title: String?, members: Collection<RecipientId>): Boolean {
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null)
return create(groupId, if (title.isNullOrEmpty()) null else title, members, null, null, null, null, null)
}
@CheckReturnValue
fun create(groupMasterKey: GroupMasterKey, groupState: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?): GroupId.V2? {
@JvmOverloads
fun create(
groupMasterKey: GroupMasterKey,
groupState: DecryptedGroup,
groupSendEndorsements: ReceivedGroupSendEndorsements?,
verifiedNameHash: ByteArray? = null
): GroupId.V2? {
val groupId = GroupId.v2(groupMasterKey)
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements)) {
return if (create(groupId = groupId, title = groupState.title, memberCollection = emptyList(), avatar = null, groupMasterKey = groupMasterKey, groupState = groupState, receivedGroupSendEndorsements = groupSendEndorsements, verifiedNameHash = verifiedNameHash)) {
groupId
} else {
null
@@ -667,7 +690,8 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
avatar: SignalServiceAttachmentPointer?,
groupMasterKey: GroupMasterKey?,
groupState: DecryptedGroup?,
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?,
verifiedNameHash: ByteArray?
): Boolean {
val membershipValues = mutableListOf<ContentValues>()
val groupRecipientId = recipients.getOrInsertFromGroupId(groupId)
@@ -716,6 +740,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
values.put(V2_MASTER_KEY, groupMasterKey.serialize())
values.put(V2_REVISION, groupState.revision)
values.put(V2_DECRYPTED_GROUP, groupState.encode())
values.put(V2_VERIFIED_NAME_HASH, verifiedNameHash)
membershipValues.clear()
membershipValues.addAll(groupMembers.toContentValues(groupId, receivedGroupSendEndorsements?.toGroupSendEndorsementRecords()))
} else {
@@ -787,11 +812,25 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
notifyConversationListListeners()
}
fun update(groupMasterKey: GroupMasterKey, decryptedGroup: DecryptedGroup, groupSendEndorsements: ReceivedGroupSendEndorsements?, terminatorRecipientId: RecipientId? = null) {
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements, terminatorRecipientId)
@JvmOverloads
fun update(
groupMasterKey: GroupMasterKey,
decryptedGroup: DecryptedGroup,
groupSendEndorsements: ReceivedGroupSendEndorsements?,
terminatorRecipientId: RecipientId? = null,
selfAuthoredTitle: Boolean = false
) {
update(GroupId.v2(groupMasterKey), decryptedGroup, groupSendEndorsements, terminatorRecipientId, selfAuthoredTitle)
}
fun update(groupId: GroupId.V2, decryptedGroup: DecryptedGroup, receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?, terminatorRecipientId: RecipientId? = null) {
@JvmOverloads
fun update(
groupId: GroupId.V2,
decryptedGroup: DecryptedGroup,
receivedGroupSendEndorsements: ReceivedGroupSendEndorsements?,
terminatorRecipientId: RecipientId? = null,
selfAuthoredTitle: Boolean = false
) {
val groupRecipientId: RecipientId = recipients.getOrInsertFromGroupId(groupId)
val existingGroup: Optional<GroupRecord> = getGroup(groupId)
val title: String = decryptedGroup.title
@@ -803,6 +842,10 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
contentValues.put(IS_MEMBER, if (isGroupMember(decryptedGroup)) 1 else 0)
contentValues.put(TERMINATED_BY, terminatorRecipientId?.toLong() ?: if (decryptedGroup.terminated) -1 else 0)
if (selfAuthoredTitle) {
contentValues.put(V2_VERIFIED_NAME_HASH, computeVerifiedNameHash(title))
}
if (receivedGroupSendEndorsements != null) {
contentValues.put(GROUP_SEND_ENDORSEMENTS_EXPIRATION, receivedGroupSendEndorsements.expirationMs)
}
@@ -1165,6 +1208,7 @@ class GroupTable(context: Context?, databaseHelper: SignalDatabase?) :
groupMasterKeyBytes = cursor.requireBlob(V2_MASTER_KEY),
groupRevision = cursor.requireInt(V2_REVISION),
decryptedGroupBytes = cursor.requireBlob(V2_DECRYPTED_GROUP),
verifiedNameHash = cursor.requireBlob(V2_VERIFIED_NAME_HASH),
distributionId = cursor.optionalString(DISTRIBUTION_ID).map { id -> DistributionId.from(id) }.orElse(null),
lastForceUpdateTimestamp = cursor.requireLong(LAST_FORCE_UPDATE_TIMESTAMP),
groupSendEndorsementExpiration = cursor.requireLong(GROUP_SEND_ENDORSEMENTS_EXPIRATION)

View File

@@ -1013,6 +1013,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
val masterKey = GroupMasterKey(insert.proto.masterKey.toByteArray())
val groupId = GroupId.v2(masterKey)
val values = getValuesForStorageGroupV2(insert, true)
val verifiedNameHash: ByteArray? = insert.proto.verifiedNameHash.nullIfEmpty()?.toByteArray()
val createdId = writableDatabase.withinTransaction {
writableDatabase.insertOrThrow(TABLE_NAME, null, values)
@@ -1021,12 +1022,14 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
groups.create(
groupMasterKey = masterKey,
groupState = DecryptedGroup(revision = GroupsV2StateProcessor.RESTORE_PLACEHOLDER_REVISION),
groupSendEndorsements = null
groupSendEndorsements = null,
verifiedNameHash = verifiedNameHash
)
}
if (createdId == null) {
Log.w(TAG, "Unable to create restore placeholder for $groupId, group already exists")
groups.setVerifiedGroupNameHash(groupId, verifiedNameHash)
}
groups.setShowAsStoryState(groupId, insert.proto.storySendMode.toShowAsStoryState())
@@ -1040,7 +1043,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
Log.i(TAG, "Scheduling request for latest group info for $groupId")
AppDependencies.jobManager.add(RequestGroupV2InfoJob(groupId))
threads.applyStorageSyncUpdate(recipient.id, insert)
recipient.live().refresh()
AppDependencies.databaseObserver.notifyRecipientChanged(recipient.id)
}
fun applyStorageSyncGroupV2Update(update: StorageRecordUpdate<SignalGroupV2Record>) {
@@ -1060,8 +1063,9 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
groups.setShowAsStoryState(groupId, update.new.proto.storySendMode.toShowAsStoryState())
groups.setVerifiedGroupNameHash(groupId, update.new.proto.verifiedNameHash.nullIfEmpty()?.toByteArray())
threads.applyStorageSyncUpdate(recipient.id, update.new)
recipient.live().refresh()
AppDependencies.databaseObserver.notifyRecipientChanged(recipient.id)
}
fun applyStorageSyncAccountUpdate(update: StorageRecordUpdate<SignalAccountRecord>) {

View File

@@ -168,6 +168,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V312_RefactorNameCo
import org.thoughtcrime.securesms.database.helpers.migration.V313_AddCollapsingUpdateColumns
import org.thoughtcrime.securesms.database.helpers.migration.V314_FixMessageRequestAcceptedToRecipient
import org.thoughtcrime.securesms.database.helpers.migration.V315_CleanupE164SenderKeyShared
import org.thoughtcrime.securesms.database.helpers.migration.V316_AddVerifiedGroupNameHashMigration
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
/**
@@ -343,10 +344,11 @@ object SignalDatabaseMigrations {
312 to V312_RefactorNameCollisionTables,
313 to V313_AddCollapsingUpdateColumns,
314 to V314_FixMessageRequestAcceptedToRecipient,
315 to V315_CleanupE164SenderKeyShared
315 to V315_CleanupE164SenderKeyShared,
316 to V316_AddVerifiedGroupNameHashMigration
)
const val DATABASE_VERSION = 315
const val DATABASE_VERSION = 316
@JvmStatic
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,11 @@
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import org.thoughtcrime.securesms.database.SQLiteDatabase
@Suppress("ClassName")
object V316_AddVerifiedGroupNameHashMigration : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE groups ADD COLUMN verified_name_hash BLOB DEFAULT NULL")
}
}

View File

@@ -31,6 +31,7 @@ class GroupRecord(
groupMasterKeyBytes: ByteArray?,
groupRevision: Int,
decryptedGroupBytes: ByteArray?,
val verifiedNameHash: ByteArray? = null,
val distributionId: DistributionId?,
val lastForceUpdateTimestamp: Long,
val groupSendEndorsementExpiration: Long

View File

@@ -53,6 +53,7 @@ import org.thoughtcrime.securesms.profiles.AvatarHelper;
import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.storage.StorageSyncHelper;
import org.thoughtcrime.securesms.util.ProfileUtil;
import org.whispersystems.signalservice.api.groupsv2.DecryptChangeVerificationMode;
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupExtensions;
@@ -235,7 +236,7 @@ final class GroupManagerV2 {
DecryptedGroup decryptedGroup = createGroupResponse.getGroup();
GroupMasterKey masterKey = groupSecretParams.getMasterKey();
ReceivedGroupSendEndorsements groupSendEndorsements = groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroup, createGroupResponse.getGroupSendEndorsementsResponse());
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements);
GroupId.V2 groupId = groupDatabase.create(masterKey, decryptedGroup, groupSendEndorsements, GroupTable.computeVerifiedNameHash(decryptedGroup.title));
if (groupId == null) {
throw new GroupChangeFailedException("Unable to create group, group already exists");
@@ -745,7 +746,14 @@ final class GroupManagerV2 {
}
RecipientId terminatorRecipientId = (decryptedGroupState.terminated && !previousGroupState.terminated) ? Recipient.self().getId() : null;
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.group_send_endorsements_response), terminatorRecipientId);
boolean selfAuthoredTitle = changeActions.modifyTitle != null;
groupDatabase.update(groupId, decryptedGroupState, groupsV2Operations.forGroup(groupSecretParams).receiveGroupSendEndorsements(selfAci, decryptedGroupState, changeResponse.group_send_endorsements_response), terminatorRecipientId, selfAuthoredTitle);
if (selfAuthoredTitle) {
RecipientId groupRecipientId = SignalDatabase.recipients().getOrInsertFromGroupId(groupId);
SignalDatabase.recipients().rotateStorageId(groupRecipientId, false);
StorageSyncHelper.scheduleSyncForDataChange();
}
GroupMutation groupMutation = new GroupMutation(previousGroupState, decryptedChange, decryptedGroupState);
RecipientAndThread recipientAndThread = sendGroupUpdateHelper.sendGroupUpdate(groupMasterKey, groupMutation, signedGroupChange, sendToMembers);

View File

@@ -18,6 +18,7 @@ import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.signal.libsignal.zkgroup.groups.GroupSecretParams
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroup
import org.signal.storageservice.storage.protos.groups.local.DecryptedGroupChange
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.database.model.GroupsV2UpdateMessageConverter
@@ -42,6 +43,7 @@ import org.thoughtcrime.securesms.mms.MmsException
import org.thoughtcrime.securesms.mms.OutgoingMessage
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.thoughtcrime.securesms.storage.StorageSyncHelper
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.whispersystems.signalservice.api.NetworkResult
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
@@ -635,6 +637,7 @@ class GroupsV2StateProcessor private constructor(
val terminatorRecipientId: RecipientId? = if (wasTerminated) {
groupStateDiff
.serverHistory
.asSequence()
.mapNotNull { it.change }
.firstOrNull { it.terminateGroup }
?.let { ServiceId.parseOrNull(it.editorServiceIdBytes) }
@@ -643,17 +646,42 @@ class GroupsV2StateProcessor private constructor(
null
}
val selfAuthoredTitle: Boolean = run {
val lastTitleChange = groupStateDiff
.serverHistory
.asSequence()
.mapNotNull { it.change }
.lastOrNull { it.newTitle != null }
if (lastTitleChange != null) {
return@run ServiceId.parseOrNull(lastTitleChange.editorServiceIdBytes) == serviceIds.aci
}
if (previousGroupState == null && updatedGroupState.revision == 0) {
val rev0Editor = groupStateDiff
.serverHistory
.firstOrNull { it.group?.revision == 0 }
?.change
?.let { ServiceId.parseOrNull(it.editorServiceIdBytes) }
return@run rev0Editor == serviceIds.aci
}
false
}
val needsAvatarFetch = if (previousGroupState == null) {
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements)
val verifiedNameHash: ByteArray? = if (selfAuthoredTitle) GroupTable.computeVerifiedNameHash(updatedGroupState.title) else null
val groupId = SignalDatabase.groups.create(groupMasterKey, updatedGroupState, groupSendEndorsements, verifiedNameHash)
if (groupId == null) {
Log.w(TAG, "$logPrefix Group create failed, trying to update")
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId, selfAuthoredTitle)
}
updatedGroupState.avatar.isNotEmpty()
} else {
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId)
SignalDatabase.groups.update(groupMasterKey, updatedGroupState, groupSendEndorsements, terminatorRecipientId, selfAuthoredTitle)
updatedGroupState.avatar != previousGroupState.avatar
}
@@ -667,6 +695,10 @@ class GroupsV2StateProcessor private constructor(
AppDependencies.jobManager.add(AvatarGroupsV2DownloadJob(groupId, updatedGroupState.avatar))
}
if (selfAuthoredTitle) {
profileAndMessageHelper.scheduleStorageServiceSync()
}
profileAndMessageHelper.setProfileSharing(groupStateDiff, updatedGroupState, needsAvatarFetch)
}
@@ -1005,6 +1037,13 @@ class GroupsV2StateProcessor private constructor(
return Optional.empty()
}
fun scheduleStorageServiceSync() {
val groupRecipientId = SignalDatabase.recipients.getOrInsertFromGroupId(groupId)
SignalDatabase.recipients.rotateStorageId(groupRecipientId)
Recipient.live(groupRecipientId).refresh()
StorageSyncHelper.scheduleSyncForDataChange()
}
companion object {
@VisibleForTesting
fun create(aci: ACI, masterKey: GroupMasterKey, groupId: GroupId.V2): ProfileAndMessageHelper {

View File

@@ -12,7 +12,8 @@ class GroupInfo(
val hasExistingContacts: Boolean = false,
val membersPreview: List<Recipient> = emptyList(),
val isMember: Boolean = false,
val isTerminated: Boolean = false
val isTerminated: Boolean = false,
val nameVerified: Boolean = false
) {
companion object {
@JvmField

View File

@@ -31,11 +31,10 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.recipients.RecipientId;
import org.thoughtcrime.securesms.recipients.RecipientUtil;
import org.thoughtcrime.securesms.sms.MessageSender;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.internal.push.exceptions.GroupPatchNotAcceptedException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.Executor;
@@ -73,12 +72,13 @@ public final class MessageRequestRepository {
boolean groupHasExistingContacts = recipients.stream().filter(r -> !r.isSelf()).anyMatch(r -> r.isProfileSharing() || r.isSystemContact());
List<Recipient> membersPreview = recipients.stream().filter(r -> !r.isSelf()).limit(MAX_MEMBER_NAMES).collect(Collectors.toList());
DecryptedGroup decryptedGroup = groupRecord.get().requireV2GroupProperties().getDecryptedGroup();
boolean nameVerified = groupRecord.get().getVerifiedNameHash() != null && Arrays.equals(GroupTable.computeVerifiedNameHash(groupRecord.get().getTitle()), groupRecord.get().getVerifiedNameHash());
groupInfo = new GroupInfo(decryptedGroup.members.size(), decryptedGroup.pendingMembers.size(), decryptedGroup.description, groupHasExistingContacts, membersPreview, groupRecord.get().isMember(), groupRecord.get().isTerminated());
groupInfo = new GroupInfo(decryptedGroup.members.size(), decryptedGroup.pendingMembers.size(), decryptedGroup.description, groupHasExistingContacts, membersPreview, groupRecord.get().isMember(), groupRecord.get().isTerminated(), nameVerified);
} else {
List<Recipient> membersPreview = recipients.stream().filter(r -> !r.isSelf()).limit(MAX_MEMBER_NAMES).collect(Collectors.toList());
groupInfo = new GroupInfo(groupRecord.get().getMembers().size(), 0, "", false, membersPreview, groupRecord.get().isActive(), false);
groupInfo = new GroupInfo(groupRecord.get().getMembers().size(), 0, "", false, membersPreview, groupRecord.get().isActive(), false, false);
}
}

View File

@@ -1,5 +1,6 @@
package org.thoughtcrime.securesms.storage
import org.signal.core.util.isNotEmpty
import org.signal.core.util.logging.Log
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.database.GroupTable
@@ -60,6 +61,7 @@ class GroupV2RecordProcessor(private val recipientTable: RecipientTable, private
hideStory = remote.proto.hideStory
storySendMode = remote.proto.storySendMode
avatarColor = if (SignalStore.account.isPrimaryDevice) local.proto.avatarColor else remote.proto.avatarColor
verifiedNameHash = if (remote.proto.verifiedNameHash.isNotEmpty()) remote.proto.verifiedNameHash else local.proto.verifiedNameHash
}.build().toSignalGroupV2Record(StorageId.forGroupV2(keyGenerator.generate()))
val matchesRemote = doParamsMatch(remote, merged)

View File

@@ -242,6 +242,8 @@ object StorageSyncModels {
throw AssertionError("Group is not V2")
}
val localVerifiedNameHash: ByteArray? = groups.getGroup(groupId).orElse(null)?.verifiedNameHash
return SignalGroupV2Record.newBuilder(recipient.syncExtras.storageProto).apply {
masterKey = groupMasterKey.serialize().toByteString()
blocked = recipient.isBlocked
@@ -257,6 +259,9 @@ object StorageSyncModels {
ShowAsStoryState.NEVER -> GroupV2Record.StorySendMode.DISABLED
else -> GroupV2Record.StorySendMode.DEFAULT
}
if (localVerifiedNameHash != null) {
verifiedNameHash = localVerifiedNameHash.toByteString()
}
}.build().toSignalGroupV2Record(StorageId.forGroupV2(rawStorageId))
}