1 package org
.asamk
.signal
.manager
.helper
;
3 import com
.google
.protobuf
.InvalidProtocolBufferException
;
5 import org
.asamk
.signal
.manager
.groups
.GroupLinkPassword
;
6 import org
.asamk
.signal
.manager
.groups
.GroupLinkState
;
7 import org
.asamk
.signal
.manager
.groups
.GroupPermission
;
8 import org
.asamk
.signal
.manager
.groups
.GroupUtils
;
9 import org
.asamk
.signal
.manager
.storage
.groups
.GroupInfoV2
;
10 import org
.asamk
.signal
.manager
.storage
.recipients
.Profile
;
11 import org
.asamk
.signal
.manager
.storage
.recipients
.RecipientId
;
12 import org
.asamk
.signal
.manager
.util
.IOUtils
;
13 import org
.signal
.storageservice
.protos
.groups
.AccessControl
;
14 import org
.signal
.storageservice
.protos
.groups
.GroupChange
;
15 import org
.signal
.storageservice
.protos
.groups
.Member
;
16 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroup
;
17 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupChange
;
18 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedGroupJoinInfo
;
19 import org
.signal
.storageservice
.protos
.groups
.local
.DecryptedPendingMember
;
20 import org
.signal
.zkgroup
.InvalidInputException
;
21 import org
.signal
.zkgroup
.VerificationFailedException
;
22 import org
.signal
.zkgroup
.groups
.GroupMasterKey
;
23 import org
.signal
.zkgroup
.groups
.GroupSecretParams
;
24 import org
.signal
.zkgroup
.groups
.UuidCiphertext
;
25 import org
.slf4j
.Logger
;
26 import org
.slf4j
.LoggerFactory
;
27 import org
.whispersystems
.libsignal
.util
.Pair
;
28 import org
.whispersystems
.libsignal
.util
.guava
.Optional
;
29 import org
.whispersystems
.signalservice
.api
.groupsv2
.DecryptedGroupUtil
;
30 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupCandidate
;
31 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupLinkNotActiveException
;
32 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Api
;
33 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2AuthorizationString
;
34 import org
.whispersystems
.signalservice
.api
.groupsv2
.GroupsV2Operations
;
35 import org
.whispersystems
.signalservice
.api
.groupsv2
.InvalidGroupStateException
;
36 import org
.whispersystems
.signalservice
.api
.groupsv2
.NotAbleToApplyGroupV2ChangeException
;
37 import org
.whispersystems
.signalservice
.api
.push
.SignalServiceAddress
;
38 import org
.whispersystems
.signalservice
.api
.util
.UuidUtil
;
41 import java
.io
.FileInputStream
;
42 import java
.io
.IOException
;
43 import java
.io
.InputStream
;
45 import java
.util
.UUID
;
46 import java
.util
.stream
.Collectors
;
48 public class GroupV2Helper
{
50 private final static Logger logger
= LoggerFactory
.getLogger(GroupV2Helper
.class);
52 private final ProfileKeyCredentialProvider profileKeyCredentialProvider
;
54 private final ProfileProvider profileProvider
;
56 private final SelfRecipientIdProvider selfRecipientIdProvider
;
58 private final GroupsV2Operations groupsV2Operations
;
60 private final GroupsV2Api groupsV2Api
;
62 private final GroupAuthorizationProvider groupAuthorizationProvider
;
64 private final SignalServiceAddressResolver addressResolver
;
67 final ProfileKeyCredentialProvider profileKeyCredentialProvider
,
68 final ProfileProvider profileProvider
,
69 final SelfRecipientIdProvider selfRecipientIdProvider
,
70 final GroupsV2Operations groupsV2Operations
,
71 final GroupsV2Api groupsV2Api
,
72 final GroupAuthorizationProvider groupAuthorizationProvider
,
73 final SignalServiceAddressResolver addressResolver
75 this.profileKeyCredentialProvider
= profileKeyCredentialProvider
;
76 this.profileProvider
= profileProvider
;
77 this.selfRecipientIdProvider
= selfRecipientIdProvider
;
78 this.groupsV2Operations
= groupsV2Operations
;
79 this.groupsV2Api
= groupsV2Api
;
80 this.groupAuthorizationProvider
= groupAuthorizationProvider
;
81 this.addressResolver
= addressResolver
;
84 public DecryptedGroup
getDecryptedGroup(final GroupSecretParams groupSecretParams
) {
86 final var groupsV2AuthorizationString
= groupAuthorizationProvider
.getAuthorizationForToday(
88 return groupsV2Api
.getGroup(groupSecretParams
, groupsV2AuthorizationString
);
89 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
90 logger
.warn("Failed to retrieve Group V2 info, ignoring: {}", e
.getMessage());
95 public DecryptedGroupJoinInfo
getDecryptedGroupJoinInfo(
96 GroupMasterKey groupMasterKey
, GroupLinkPassword password
97 ) throws IOException
, GroupLinkNotActiveException
{
98 var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
100 return groupsV2Api
.getGroupJoinInfo(groupSecretParams
,
101 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
),
102 groupAuthorizationProvider
.getAuthorizationForToday(groupSecretParams
));
105 public Pair
<GroupInfoV2
, DecryptedGroup
> createGroup(
106 String name
, Set
<RecipientId
> members
, File avatarFile
107 ) throws IOException
{
108 final var avatarBytes
= readAvatarBytes(avatarFile
);
109 final var newGroup
= buildNewGroup(name
, members
, avatarBytes
);
110 if (newGroup
== null) {
114 final var groupSecretParams
= newGroup
.getGroupSecretParams();
116 final GroupsV2AuthorizationString groupAuthForToday
;
117 final DecryptedGroup decryptedGroup
;
119 groupAuthForToday
= groupAuthorizationProvider
.getAuthorizationForToday(groupSecretParams
);
120 groupsV2Api
.putNewGroup(newGroup
, groupAuthForToday
);
121 decryptedGroup
= groupsV2Api
.getGroup(groupSecretParams
, groupAuthForToday
);
122 } catch (IOException
| VerificationFailedException
| InvalidGroupStateException e
) {
123 logger
.warn("Failed to create V2 group: {}", e
.getMessage());
126 if (decryptedGroup
== null) {
127 logger
.warn("Failed to create V2 group, unknown error!");
131 final var groupId
= GroupUtils
.getGroupIdV2(groupSecretParams
);
132 final var masterKey
= groupSecretParams
.getMasterKey();
133 var g
= new GroupInfoV2(groupId
, masterKey
);
135 return new Pair
<>(g
, decryptedGroup
);
138 private byte[] readAvatarBytes(final File avatarFile
) throws IOException
{
139 final byte[] avatarBytes
;
140 try (InputStream avatar
= avatarFile
== null ?
null : new FileInputStream(avatarFile
)) {
141 avatarBytes
= avatar
== null ?
null : IOUtils
.readFully(avatar
);
146 private GroupsV2Operations
.NewGroup
buildNewGroup(
147 String name
, Set
<RecipientId
> members
, byte[] avatar
149 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientIdProvider
.getSelfRecipientId());
150 if (profileKeyCredential
== null) {
151 logger
.warn("Cannot create a V2 group as self does not have a versioned profile");
155 if (!areMembersValid(members
)) return null;
157 var self
= new GroupCandidate(addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
159 .orNull(), Optional
.fromNullable(profileKeyCredential
));
160 var candidates
= members
.stream()
161 .map(member
-> new GroupCandidate(addressResolver
.resolveSignalServiceAddress(member
).getUuid().get(),
162 Optional
.fromNullable(profileKeyCredentialProvider
.getProfileKeyCredential(member
))))
163 .collect(Collectors
.toSet());
165 final var groupSecretParams
= GroupSecretParams
.generate();
166 return groupsV2Operations
.createNewGroup(groupSecretParams
,
168 Optional
.fromNullable(avatar
),
175 private boolean areMembersValid(final Set
<RecipientId
> members
) {
176 final var noUuidCapability
= members
.stream()
177 .map(addressResolver
::resolveSignalServiceAddress
)
178 .filter(address
-> !address
.getUuid().isPresent())
179 .map(SignalServiceAddress
::getNumber
)
181 .collect(Collectors
.toSet());
182 if (noUuidCapability
.size() > 0) {
183 logger
.warn("Cannot create a V2 group as some members don't have a UUID: {}",
184 String
.join(", ", noUuidCapability
));
188 final var noGv2Capability
= members
.stream()
189 .map(profileProvider
::getProfile
)
190 .filter(profile
-> profile
!= null && !profile
.getCapabilities().contains(Profile
.Capability
.gv2
))
191 .collect(Collectors
.toSet());
192 if (noGv2Capability
.size() > 0) {
193 logger
.warn("Cannot create a V2 group as some members don't support Groups V2: {}",
194 noGv2Capability
.stream().map(Profile
::getDisplayName
).collect(Collectors
.joining(", ")));
201 public Pair
<DecryptedGroup
, GroupChange
> updateGroup(
202 GroupInfoV2 groupInfoV2
, String name
, String description
, File avatarFile
203 ) throws IOException
{
204 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
205 var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
207 var change
= name
!= null ? groupOperations
.createModifyGroupTitle(name
) : GroupChange
.Actions
.newBuilder();
209 if (description
!= null) {
210 change
.setModifyDescription(groupOperations
.createModifyGroupDescriptionAction(description
));
213 if (avatarFile
!= null) {
214 final var avatarBytes
= readAvatarBytes(avatarFile
);
215 var avatarCdnKey
= groupsV2Api
.uploadAvatar(avatarBytes
,
217 groupAuthorizationProvider
.getAuthorizationForToday(groupSecretParams
));
218 change
.setModifyAvatar(GroupChange
.Actions
.ModifyAvatarAction
.newBuilder().setAvatar(avatarCdnKey
));
221 final var uuid
= addressResolver
.resolveSignalServiceAddress(this.selfRecipientIdProvider
.getSelfRecipientId())
223 if (uuid
.isPresent()) {
224 change
.setSourceUuid(UuidUtil
.toByteString(uuid
.get()));
227 return commitChange(groupInfoV2
, change
);
230 public Pair
<DecryptedGroup
, GroupChange
> addMembers(
231 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> newMembers
232 ) throws IOException
{
233 GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
235 if (!areMembersValid(newMembers
)) {
236 throw new IOException("Failed to update group");
239 var candidates
= newMembers
.stream()
240 .map(member
-> new GroupCandidate(addressResolver
.resolveSignalServiceAddress(member
).getUuid().get(),
241 Optional
.fromNullable(profileKeyCredentialProvider
.getProfileKeyCredential(member
))))
242 .collect(Collectors
.toSet());
244 final var uuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
247 final var change
= groupOperations
.createModifyGroupMembershipChange(candidates
, uuid
);
249 change
.setSourceUuid(UuidUtil
.toByteString(uuid
));
251 return commitChange(groupInfoV2
, change
);
254 public Pair
<DecryptedGroup
, GroupChange
> leaveGroup(
255 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> membersToMakeAdmin
256 ) throws IOException
{
257 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
258 final var selfUuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
261 var selfPendingMember
= DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, selfUuid
);
263 if (selfPendingMember
.isPresent()) {
264 return revokeInvites(groupInfoV2
, Set
.of(selfPendingMember
.get()));
267 final var adminUuids
= membersToMakeAdmin
.stream()
268 .map(addressResolver
::resolveSignalServiceAddress
)
269 .map(SignalServiceAddress
::getUuid
)
271 .collect(Collectors
.toList());
272 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
273 return commitChange(groupInfoV2
, groupOperations
.createLeaveAndPromoteMembersToAdmin(selfUuid
, adminUuids
));
276 public Pair
<DecryptedGroup
, GroupChange
> removeMembers(
277 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
278 ) throws IOException
{
279 final var memberUuids
= members
.stream()
280 .map(addressResolver
::resolveSignalServiceAddress
)
281 .map(SignalServiceAddress
::getUuid
)
282 .filter(Optional
::isPresent
)
284 .collect(Collectors
.toSet());
285 return ejectMembers(groupInfoV2
, memberUuids
);
288 public Pair
<DecryptedGroup
, GroupChange
> revokeInvitedMembers(
289 GroupInfoV2 groupInfoV2
, Set
<RecipientId
> members
290 ) throws IOException
{
291 var pendingMembersList
= groupInfoV2
.getGroup().getPendingMembersList();
292 final var memberUuids
= members
.stream()
293 .map(addressResolver
::resolveSignalServiceAddress
)
294 .map(SignalServiceAddress
::getUuid
)
295 .filter(Optional
::isPresent
)
297 .map(uuid
-> DecryptedGroupUtil
.findPendingByUuid(pendingMembersList
, uuid
))
298 .filter(Optional
::isPresent
)
300 .collect(Collectors
.toSet());
301 return revokeInvites(groupInfoV2
, memberUuids
);
304 public Pair
<DecryptedGroup
, GroupChange
> resetGroupLinkPassword(GroupInfoV2 groupInfoV2
) throws IOException
{
305 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
306 final var newGroupLinkPassword
= GroupLinkPassword
.createNew().serialize();
307 final var change
= groupOperations
.createModifyGroupLinkPasswordChange(newGroupLinkPassword
);
308 return commitChange(groupInfoV2
, change
);
311 public Pair
<DecryptedGroup
, GroupChange
> setGroupLinkState(
312 GroupInfoV2 groupInfoV2
, GroupLinkState state
313 ) throws IOException
{
314 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
316 final var accessRequired
= toAccessControl(state
);
317 final var requiresNewPassword
= state
!= GroupLinkState
.DISABLED
&& groupInfoV2
.getGroup()
318 .getInviteLinkPassword()
321 final var change
= requiresNewPassword ? groupOperations
.createModifyGroupLinkPasswordAndRightsChange(
322 GroupLinkPassword
.createNew().serialize(),
323 accessRequired
) : groupOperations
.createChangeJoinByLinkRights(accessRequired
);
324 return commitChange(groupInfoV2
, change
);
327 public Pair
<DecryptedGroup
, GroupChange
> setEditDetailsPermission(
328 GroupInfoV2 groupInfoV2
, GroupPermission permission
329 ) throws IOException
{
330 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
332 final var accessRequired
= toAccessControl(permission
);
333 final var change
= groupOperations
.createChangeAttributesRights(accessRequired
);
334 return commitChange(groupInfoV2
, change
);
337 public Pair
<DecryptedGroup
, GroupChange
> setAddMemberPermission(
338 GroupInfoV2 groupInfoV2
, GroupPermission permission
339 ) throws IOException
{
340 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
342 final var accessRequired
= toAccessControl(permission
);
343 final var change
= groupOperations
.createChangeMembershipRights(accessRequired
);
344 return commitChange(groupInfoV2
, change
);
347 public GroupChange
joinGroup(
348 GroupMasterKey groupMasterKey
,
349 GroupLinkPassword groupLinkPassword
,
350 DecryptedGroupJoinInfo decryptedGroupJoinInfo
351 ) throws IOException
{
352 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupMasterKey
);
353 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
355 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
356 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientId
);
357 if (profileKeyCredential
== null) {
358 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
361 var requestToJoin
= decryptedGroupJoinInfo
.getAddFromInviteLink() == AccessControl
.AccessRequired
.ADMINISTRATOR
;
362 var change
= requestToJoin
363 ? groupOperations
.createGroupJoinRequest(profileKeyCredential
)
364 : groupOperations
.createGroupJoinDirect(profileKeyCredential
);
366 change
.setSourceUuid(UuidUtil
.toByteString(addressResolver
.resolveSignalServiceAddress(selfRecipientId
)
370 return commitChange(groupSecretParams
, decryptedGroupJoinInfo
.getRevision(), change
, groupLinkPassword
);
373 public Pair
<DecryptedGroup
, GroupChange
> acceptInvite(GroupInfoV2 groupInfoV2
) throws IOException
{
374 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
376 final var selfRecipientId
= this.selfRecipientIdProvider
.getSelfRecipientId();
377 final var profileKeyCredential
= profileKeyCredentialProvider
.getProfileKeyCredential(selfRecipientId
);
378 if (profileKeyCredential
== null) {
379 throw new IOException("Cannot join a V2 group as self does not have a versioned profile");
382 final var change
= groupOperations
.createAcceptInviteChange(profileKeyCredential
);
384 final var uuid
= addressResolver
.resolveSignalServiceAddress(selfRecipientId
).getUuid();
385 if (uuid
.isPresent()) {
386 change
.setSourceUuid(UuidUtil
.toByteString(uuid
.get()));
389 return commitChange(groupInfoV2
, change
);
392 public Pair
<DecryptedGroup
, GroupChange
> setMemberAdmin(
393 GroupInfoV2 groupInfoV2
, RecipientId recipientId
, boolean admin
394 ) throws IOException
{
395 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
396 final var address
= addressResolver
.resolveSignalServiceAddress(recipientId
);
397 final var newRole
= admin ? Member
.Role
.ADMINISTRATOR
: Member
.Role
.DEFAULT
;
398 final var change
= groupOperations
.createChangeMemberRole(address
.getUuid().get(), newRole
);
399 return commitChange(groupInfoV2
, change
);
402 public Pair
<DecryptedGroup
, GroupChange
> setMessageExpirationTimer(
403 GroupInfoV2 groupInfoV2
, int messageExpirationTimer
404 ) throws IOException
{
405 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
406 final var change
= groupOperations
.createModifyGroupTimerChange(messageExpirationTimer
);
407 return commitChange(groupInfoV2
, change
);
410 public Pair
<DecryptedGroup
, GroupChange
> setIsAnnouncementGroup(
411 GroupInfoV2 groupInfoV2
, boolean isAnnouncementGroup
412 ) throws IOException
{
413 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
414 final var change
= groupOperations
.createAnnouncementGroupChange(isAnnouncementGroup
);
415 return commitChange(groupInfoV2
, change
);
418 private AccessControl
.AccessRequired
toAccessControl(final GroupLinkState state
) {
421 return AccessControl
.AccessRequired
.UNSATISFIABLE
;
423 return AccessControl
.AccessRequired
.ANY
;
424 case ENABLED_WITH_APPROVAL
:
425 return AccessControl
.AccessRequired
.ADMINISTRATOR
;
427 throw new AssertionError();
431 private AccessControl
.AccessRequired
toAccessControl(final GroupPermission permission
) {
432 switch (permission
) {
434 return AccessControl
.AccessRequired
.MEMBER
;
436 return AccessControl
.AccessRequired
.ADMINISTRATOR
;
438 throw new AssertionError();
442 private GroupsV2Operations
.GroupOperations
getGroupOperations(final GroupInfoV2 groupInfoV2
) {
443 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
444 return groupsV2Operations
.forGroup(groupSecretParams
);
447 private Pair
<DecryptedGroup
, GroupChange
> revokeInvites(
448 GroupInfoV2 groupInfoV2
, Set
<DecryptedPendingMember
> pendingMembers
449 ) throws IOException
{
450 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
451 final var uuidCipherTexts
= pendingMembers
.stream().map(member
-> {
453 return new UuidCiphertext(member
.getUuidCipherText().toByteArray());
454 } catch (InvalidInputException e
) {
455 throw new AssertionError(e
);
457 }).collect(Collectors
.toSet());
458 return commitChange(groupInfoV2
, groupOperations
.createRemoveInvitationChange(uuidCipherTexts
));
461 private Pair
<DecryptedGroup
, GroupChange
> ejectMembers(
462 GroupInfoV2 groupInfoV2
, Set
<UUID
> uuids
463 ) throws IOException
{
464 final GroupsV2Operations
.GroupOperations groupOperations
= getGroupOperations(groupInfoV2
);
465 return commitChange(groupInfoV2
, groupOperations
.createRemoveMembersChange(uuids
));
468 private Pair
<DecryptedGroup
, GroupChange
> commitChange(
469 GroupInfoV2 groupInfoV2
, GroupChange
.Actions
.Builder change
470 ) throws IOException
{
471 final var groupSecretParams
= GroupSecretParams
.deriveFromMasterKey(groupInfoV2
.getMasterKey());
472 final var groupOperations
= groupsV2Operations
.forGroup(groupSecretParams
);
473 final var previousGroupState
= groupInfoV2
.getGroup();
474 final var nextRevision
= previousGroupState
.getRevision() + 1;
475 final var changeActions
= change
.setRevision(nextRevision
).build();
476 final DecryptedGroupChange decryptedChange
;
477 final DecryptedGroup decryptedGroupState
;
480 decryptedChange
= groupOperations
.decryptChange(changeActions
,
481 addressResolver
.resolveSignalServiceAddress(selfRecipientIdProvider
.getSelfRecipientId())
484 decryptedGroupState
= DecryptedGroupUtil
.apply(previousGroupState
, decryptedChange
);
485 } catch (VerificationFailedException
| InvalidGroupStateException
| NotAbleToApplyGroupV2ChangeException e
) {
486 throw new IOException(e
);
489 var signedGroupChange
= groupsV2Api
.patchGroup(changeActions
,
490 groupAuthorizationProvider
.getAuthorizationForToday(groupSecretParams
),
493 return new Pair
<>(decryptedGroupState
, signedGroupChange
);
496 private GroupChange
commitChange(
497 GroupSecretParams groupSecretParams
,
499 GroupChange
.Actions
.Builder change
,
500 GroupLinkPassword password
501 ) throws IOException
{
502 final var nextRevision
= currentRevision
+ 1;
503 final var changeActions
= change
.setRevision(nextRevision
).build();
505 return groupsV2Api
.patchGroup(changeActions
,
506 groupAuthorizationProvider
.getAuthorizationForToday(groupSecretParams
),
507 Optional
.fromNullable(password
).transform(GroupLinkPassword
::serialize
));
510 public DecryptedGroup
getUpdatedDecryptedGroup(
511 DecryptedGroup group
, byte[] signedGroupChange
, GroupMasterKey groupMasterKey
514 final var decryptedGroupChange
= getDecryptedGroupChange(signedGroupChange
, groupMasterKey
);
515 if (decryptedGroupChange
== null) {
518 return DecryptedGroupUtil
.apply(group
, decryptedGroupChange
);
519 } catch (NotAbleToApplyGroupV2ChangeException e
) {
524 private DecryptedGroupChange
getDecryptedGroupChange(byte[] signedGroupChange
, GroupMasterKey groupMasterKey
) {
525 if (signedGroupChange
!= null) {
526 var groupOperations
= groupsV2Operations
.forGroup(GroupSecretParams
.deriveFromMasterKey(groupMasterKey
));
529 return groupOperations
.decryptChange(GroupChange
.parseFrom(signedGroupChange
), true).orNull();
530 } catch (VerificationFailedException
| InvalidGroupStateException
| InvalidProtocolBufferException e
) {