Enforce limit for total number of blocked requests.

This commit is contained in:
Cody Henthorne 2022-03-22 11:08:11 -04:00 committed by Greyson Parrelli
parent b3d9a85fa2
commit 6890973ce8
14 changed files with 236 additions and 43 deletions

View file

@ -105,7 +105,7 @@ public class ApplicationDependencyProvider implements ApplicationDependencies.Pr
@Override
public @NonNull GroupsV2Operations provideGroupsV2Operations() {
return new GroupsV2Operations(provideClientZkOperations());
return new GroupsV2Operations(provideClientZkOperations(), FeatureFlags.groupLimits().getHardLimit());
}
@Override

View file

@ -283,19 +283,16 @@ public final class GroupManager {
@NonNull RecipientId recipientId)
throws GroupChangeBusyException, IOException, GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException
{
GroupDatabase.GroupRecord groupRecord = SignalDatabase.groups().requireGroup(groupId);
GroupDatabase.V2GroupProperties groupProperties = SignalDatabase.groups().requireGroup(groupId).requireV2GroupProperties();
Recipient recipient = Recipient.resolved(recipientId);
if (groupRecord.requireV2GroupProperties().getBannedMembers().contains(recipient.requireServiceId().uuid())) {
if (groupProperties.getBannedMembers().contains(recipient.requireServiceId().uuid())) {
Log.i(TAG, "Attempt to ban already banned recipient: " + recipientId);
return;
}
ByteString uuid = UuidUtil.toByteString(recipient.requireServiceId().uuid());
boolean rejectJoinRequest = groupRecord.requireV2GroupProperties().getDecryptedGroup().getRequestingMembersList().stream().anyMatch(m -> m.getUuid().equals(uuid));
try (GroupManagerV2.GroupEditor editor = new GroupManagerV2(context).edit(groupId.requireV2())) {
editor.ban(Collections.singleton(recipient.requireServiceId().uuid()), rejectJoinRequest);
editor.ban(recipient.requireServiceId().uuid());
}
}

View file

@ -309,6 +309,7 @@ final class GroupManagerV2 {
final class GroupEditor extends LockOwner {
private final GroupId.V2 groupId;
private final GroupDatabase.V2GroupProperties v2GroupProperties;
private final GroupMasterKey groupMasterKey;
private final GroupSecretParams groupSecretParams;
private final GroupsV2Operations.GroupOperations groupOperations;
@ -317,9 +318,9 @@ final class GroupManagerV2 {
super(lock);
GroupDatabase.GroupRecord groupRecord = groupDatabase.requireGroup(groupId);
GroupDatabase.V2GroupProperties v2GroupProperties = groupRecord.requireV2GroupProperties();
this.groupId = groupId;
this.v2GroupProperties = groupRecord.requireV2GroupProperties();
this.groupMasterKey = v2GroupProperties.getGroupMasterKey();
this.groupSecretParams = GroupSecretParams.deriveFromMasterKey(groupMasterKey);
this.groupOperations = groupsV2Operations.forGroup(groupSecretParams);
@ -428,7 +429,7 @@ final class GroupManagerV2 {
.map(r -> Recipient.resolved(r).requireServiceId().uuid())
.collect(Collectors.toSet());
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true));
return commitChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, true, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
}
@WorkerThread
@ -463,7 +464,11 @@ final class GroupManagerV2 {
@NonNull GroupManager.GroupActionResult ejectMember(@NonNull ServiceId serviceId, boolean allowWhenBlocked, boolean ban)
throws GroupChangeFailedException, GroupInsufficientRightsException, IOException, GroupNotAMemberException
{
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()), ban), allowWhenBlocked);
return commitChangeWithConflictResolution(groupOperations.createRemoveMembersChange(Collections.singleton(serviceId.uuid()),
ban,
ban ? v2GroupProperties.getDecryptedGroup().getBannedMembersList()
: Collections.emptyList()),
allowWhenBlocked);
}
@WorkerThread
@ -530,11 +535,18 @@ final class GroupManagerV2 {
return commitChangeWithConflictResolution(groupOperations.createAcceptInviteChange(groupCandidate.getProfileKeyCredential().get()));
}
public GroupManager.GroupActionResult ban(Set<UUID> uuids, boolean rejectJoinRequest) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException {
return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(uuids, rejectJoinRequest));
public GroupManager.GroupActionResult ban(UUID uuid)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
ByteString uuidByteString = UuidUtil.toByteString(uuid);
boolean rejectJoinRequest = v2GroupProperties.getDecryptedGroup().getRequestingMembersList().stream().anyMatch(m -> m.getUuid().equals(uuidByteString));
return commitChangeWithConflictResolution(groupOperations.createBanUuidsChange(Collections.singleton(uuid), rejectJoinRequest, v2GroupProperties.getDecryptedGroup().getBannedMembersList()));
}
public GroupManager.GroupActionResult unban(Set<UUID> uuids) throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException {
public GroupManager.GroupActionResult unban(Set<UUID> uuids)
throws GroupChangeFailedException, GroupNotAMemberException, GroupInsufficientRightsException, IOException
{
return commitChangeWithConflictResolution(groupOperations.createUnbanUuidsChange(uuids));
}
@ -1102,7 +1114,7 @@ final class GroupManagerV2 {
GroupChange signedGroupChange;
try {
signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, false));
signedGroupChange = commitCancelChangeWithConflictResolution(groupOperations.createRefuseGroupJoinRequest(uuids, false, Collections.emptyList()));
} catch (GroupLinkNotActiveException e) {
Log.d(TAG, "Unexpected unable to leave group due to group link off");
throw new GroupChangeFailedException(e);

View file

@ -43,7 +43,8 @@ public class AccountManagerFactory {
deviceId,
password,
BuildConfig.SIGNAL_AGENT,
FeatureFlags.okHttpAutomaticRetry());
FeatureFlags.okHttpAutomaticRetry(),
FeatureFlags.groupLimits().getHardLimit());
}
/**
@ -65,7 +66,14 @@ public class AccountManagerFactory {
}
return new SignalServiceAccountManager(new SignalServiceNetworkAccess(context).getConfiguration(number),
null, null, number, deviceId, password, BuildConfig.SIGNAL_AGENT, FeatureFlags.okHttpAutomaticRetry());
null,
null,
number,
deviceId,
password,
BuildConfig.SIGNAL_AGENT,
FeatureFlags.okHttpAutomaticRetry(),
FeatureFlags.groupLimits().getHardLimit());
}
}

View file

@ -85,7 +85,7 @@ class GroupManagerV2Test_edit {
groupDatabase = mock(GroupDatabase::class.java)
groupsV2API = mock(GroupsV2Api::class.java)
groupsV2Operations = GroupsV2Operations(clientZkOperations)
groupsV2Operations = GroupsV2Operations(clientZkOperations, 1000)
groupsV2Authorization = mock(GroupsV2Authorization::class.java)
groupsV2StateProcessor = mock(GroupsV2StateProcessor::class.java)
groupCandidateHelper = mock(GroupCandidateHelper::class.java)

View file

@ -146,12 +146,13 @@ public class SignalServiceAccountManager {
int deviceId,
String password,
String signalAgent,
boolean automaticNetworkRetry)
boolean automaticNetworkRetry,
int maxGroupSize)
{
this(configuration,
new StaticCredentialsProvider(aci, pni, e164, deviceId, password),
signalAgent,
new GroupsV2Operations(ClientZkOperations.create(configuration)),
new GroupsV2Operations(ClientZkOperations.create(configuration), maxGroupSize),
automaticNetworkRetry);
}

View file

@ -111,6 +111,7 @@ public final class GroupChangeReconstruct {
Set<ByteString> consistentMemberUuids = intersect(fromStateMemberUuids, toStateMemberUuids);
Set<DecryptedMember> changedMembers = intersectByUUID(subtract(toState.getMembersList(), fromState.getMembersList()), consistentMemberUuids);
Map<ByteString, DecryptedMember> membersUuidMap = uuidMap(fromState.getMembersList());
Map<ByteString, DecryptedBannedMember> bannedMembersUuidMap = bannedUuidMap(toState.getBannedMembersList());
for (DecryptedMember newState : changedMembers) {
DecryptedMember oldState = membersUuidMap.get(newState.getUuid());
@ -152,7 +153,13 @@ public final class GroupChangeReconstruct {
}
for (ByteString uuid : newBannedMemberUuids) {
builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(uuid).build());
DecryptedBannedMember.Builder newBannedBuilder = DecryptedBannedMember.newBuilder().setUuid(uuid);
DecryptedBannedMember bannedMember = bannedMembersUuidMap.get(uuid);
if (bannedMember != null) {
newBannedBuilder.setTimestamp(bannedMember.getTimestamp());
}
builder.addNewBannedMembers(newBannedBuilder);
}
return builder.build();
@ -166,6 +173,14 @@ public final class GroupChangeReconstruct {
return map;
}
private static Map<ByteString, DecryptedBannedMember> bannedUuidMap(List<DecryptedBannedMember> membersList) {
Map<ByteString, DecryptedBannedMember> map = new LinkedHashMap<>(membersList.size());
for (DecryptedBannedMember member : membersList) {
map.put(member.getUuid(), member);
}
return map;
}
private static Set<DecryptedMember> intersectByUUID(Collection<DecryptedMember> members, Set<ByteString> uuids) {
Set<DecryptedMember> result = new LinkedHashSet<>(members.size());
for (DecryptedMember member : members) {

View file

@ -44,6 +44,7 @@ import org.whispersystems.signalservice.api.util.UuidUtil;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
@ -67,12 +68,14 @@ public final class GroupsV2Operations {
private final ServerPublicParams serverPublicParams;
private final ClientZkProfileOperations clientZkProfileOperations;
private final ClientZkAuthOperations clientZkAuthOperations;
private final int maxGroupSize;
private final SecureRandom random;
public GroupsV2Operations(ClientZkOperations clientZkOperations) {
public GroupsV2Operations(ClientZkOperations clientZkOperations, int maxGroupSize) {
this.serverPublicParams = clientZkOperations.getServerPublicParams();
this.clientZkProfileOperations = clientZkOperations.getProfileOperations();
this.clientZkAuthOperations = clientZkOperations.getAuthOperations();
this.maxGroupSize = maxGroupSize;
this.random = new SecureRandom();
}
@ -209,8 +212,8 @@ public final class GroupsV2Operations {
return actions;
}
public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set<UUID> requestsToRemove, boolean alsoBan) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(requestsToRemove, false)
public GroupChange.Actions.Builder createRefuseGroupJoinRequest(Set<UUID> requestsToRemove, boolean alsoBan, List<DecryptedBannedMember> bannedMembers) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(requestsToRemove, false, bannedMembers)
: GroupChange.Actions.newBuilder();
for (UUID uuid : requestsToRemove) {
@ -235,8 +238,8 @@ public final class GroupsV2Operations {
return actions;
}
public GroupChange.Actions.Builder createRemoveMembersChange(final Set<UUID> membersToRemove, boolean alsoBan) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(membersToRemove, false)
public GroupChange.Actions.Builder createRemoveMembersChange(final Set<UUID> membersToRemove, boolean alsoBan, List<DecryptedBannedMember> bannedMembers) {
GroupChange.Actions.Builder actions = alsoBan ? createBanUuidsChange(membersToRemove, false, bannedMembers)
: GroupChange.Actions.newBuilder();
for (UUID remove: membersToRemove) {
@ -249,7 +252,7 @@ public final class GroupsV2Operations {
}
public GroupChange.Actions.Builder createLeaveAndPromoteMembersToAdmin(UUID self, List<UUID> membersToMakeAdmin) {
GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self), false);
GroupChange.Actions.Builder actions = createRemoveMembersChange(Collections.singleton(self), false, Collections.emptyList());
for (UUID member : membersToMakeAdmin) {
actions.addModifyMemberRoles(GroupChange.Actions.ModifyMemberRoleAction
@ -350,10 +353,23 @@ public final class GroupsV2Operations {
.setAnnouncementsOnly(isAnnouncementGroup));
}
public GroupChange.Actions.Builder createBanUuidsChange(Set<UUID> banUuids, boolean rejectJoinRequest) {
GroupChange.Actions.Builder builder = rejectJoinRequest ? createRefuseGroupJoinRequest(banUuids, false)
public GroupChange.Actions.Builder createBanUuidsChange(Set<UUID> banUuids, boolean rejectJoinRequest, List<DecryptedBannedMember> bannedMembersList) {
GroupChange.Actions.Builder builder = rejectJoinRequest ? createRefuseGroupJoinRequest(banUuids, false, Collections.emptyList())
: GroupChange.Actions.newBuilder();
int spacesToFree = bannedMembersList.size() + banUuids.size() - maxGroupSize;
if (spacesToFree > 0) {
List<ByteString> unban = bannedMembersList.stream()
.sorted(Comparator.comparingLong(DecryptedBannedMember::getTimestamp))
.limit(spacesToFree)
.map(DecryptedBannedMember::getUuid)
.collect(Collectors.toList());
for (ByteString uuid : unban) {
builder.addDeleteBannedMembers(GroupChange.Actions.DeleteBannedMemberAction.newBuilder().setDeletedUserId(encryptUuid(UuidUtil.fromByteString(uuid))));
}
}
for (UUID uuid : banUuids) {
builder.addAddBannedMembers(GroupChange.Actions.AddBannedMemberAction.newBuilder().setAdded(BannedMember.newBuilder().setUserId(encryptUuid(uuid)).build()));
}
@ -458,7 +474,7 @@ public final class GroupsV2Operations {
}
for (BannedMember member : group.getBannedMembersList()) {
decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(member.getUserId())).build());
decryptedBannedMembers.add(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(member.getUserId())).setTimestamp(member.getTimestamp()).build());
}
return DecryptedGroup.newBuilder()
@ -662,7 +678,7 @@ public final class GroupsV2Operations {
// Field 22
for (GroupChange.Actions.AddBannedMemberAction action : actions.getAddBannedMembersList()) {
builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getAdded().getUserId())).build());
builder.addNewBannedMembers(DecryptedBannedMember.newBuilder().setUuid(decryptUuidToByteString(action.getAdded().getUserId())).setTimestamp(action.getAdded().getTimestamp()).build());
}
// Field 23

View file

@ -35,6 +35,7 @@ message DecryptedRequestingMember {
message DecryptedBannedMember {
bytes uuid = 1;
uint64 timestamp = 2;
}
message DecryptedPendingMemberRemoval {

View file

@ -47,6 +47,7 @@ message RequestingMember {
message BannedMember {
bytes userId = 1;
uint64 timestamp = 2;
}
message AccessControl {

View file

@ -0,0 +1,142 @@
package org.whispersystems.signalservice.api.groupsv2;
import com.google.protobuf.ByteString;
import org.junit.Before;
import org.junit.Test;
import org.signal.storageservice.protos.groups.BannedMember;
import org.signal.storageservice.protos.groups.GroupChange;
import org.signal.storageservice.protos.groups.GroupChange.Actions.AddBannedMemberAction;
import org.signal.storageservice.protos.groups.GroupChange.Actions.DeleteBannedMemberAction;
import org.signal.storageservice.protos.groups.local.DecryptedBannedMember;
import org.signal.zkgroup.InvalidInputException;
import org.signal.zkgroup.groups.GroupMasterKey;
import org.signal.zkgroup.groups.GroupSecretParams;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.util.Util;
import org.whispersystems.signalservice.testutil.LibSignalLibraryUtil;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static org.hamcrest.CoreMatchers.hasItems;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.whispersystems.signalservice.api.groupsv2.ProtoTestUtils.bannedMember;
public final class GroupsV2Operations_ban_Test {
private GroupsV2Operations.GroupOperations groupOperations;
@Before
public void setup() throws InvalidInputException {
LibSignalLibraryUtil.assumeLibSignalSupportedOnOS();
TestZkGroupServer server = new TestZkGroupServer();
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32)));
ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams());
groupOperations = new GroupsV2Operations(clientZkOperations, 10).forGroup(groupSecretParams);
}
@Test
public void addBanToEmptyList() {
UUID ban = UUID.randomUUID();
GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(Collections.singleton(ban),
false,
Collections.emptyList());
assertThat(banUuidsChange.getAddBannedMembersCount(), is(1));
assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(ban)));
}
@Test
public void addBanToPartialFullList() {
UUID toBan = UUID.randomUUID();
List<DecryptedBannedMember> alreadyBanned = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
alreadyBanned.add(bannedMember(UUID.randomUUID()));
}
GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(Collections.singleton(toBan),
false,
alreadyBanned);
assertThat(banUuidsChange.getAddBannedMembersCount(), is(1));
assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(toBan)));
}
@Test
public void addBanToFullList() {
UUID toBan = UUID.randomUUID();
List<DecryptedBannedMember> alreadyBanned = new ArrayList<>(10);
DecryptedBannedMember oldest = null;
for (int i = 0; i < 10; i++) {
DecryptedBannedMember member = bannedMember(UUID.randomUUID()).toBuilder().setTimestamp(100 + i).build();
if (oldest == null) {
oldest = member;
}
alreadyBanned.add(member);
}
Collections.shuffle(alreadyBanned);
GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(Collections.singleton(toBan),
false,
alreadyBanned);
assertThat(banUuidsChange.getDeleteBannedMembersCount(), is(1));
assertThat(banUuidsChange.getDeleteBannedMembers(0).getDeletedUserId(), is(groupOperations.encryptUuid(UuidUtil.fromByteString(oldest.getUuid()))));
assertThat(banUuidsChange.getAddBannedMembersCount(), is(1));
assertThat(banUuidsChange.getAddBannedMembers(0).getAdded().getUserId(), is(groupOperations.encryptUuid(toBan)));
}
@Test
public void addMultipleBanToFullList() {
List<UUID> toBan = new ArrayList<>();
toBan.add(UUID.randomUUID());
toBan.add(UUID.randomUUID());
List<DecryptedBannedMember> alreadyBanned = new ArrayList<>(10);
for (int i = 0; i < 10; i++) {
alreadyBanned.add(bannedMember(UUID.randomUUID()).toBuilder().setTimestamp(100 + i).build());
}
List<ByteString> oldest = new ArrayList<>(2);
oldest.add(groupOperations.encryptUuid(UuidUtil.fromByteString(alreadyBanned.get(0).getUuid())));
oldest.add(groupOperations.encryptUuid(UuidUtil.fromByteString(alreadyBanned.get(1).getUuid())));
Collections.shuffle(alreadyBanned);
GroupChange.Actions.Builder banUuidsChange = groupOperations.createBanUuidsChange(new HashSet<>(toBan),
false,
alreadyBanned);
assertThat(banUuidsChange.getDeleteBannedMembersCount(), is(2));
assertThat(banUuidsChange.getDeleteBannedMembersList()
.stream()
.map(DeleteBannedMemberAction::getDeletedUserId)
.collect(Collectors.toList()),
hasItems(oldest.get(0), oldest.get(1)));
assertThat(banUuidsChange.getAddBannedMembersCount(), is(2));
assertThat(banUuidsChange.getAddBannedMembersList()
.stream()
.map(AddBannedMemberAction::getAdded)
.map(BannedMember::getUserId)
.collect(Collectors.toList()),
hasItems(groupOperations.encryptUuid(toBan.get(0)), groupOperations.encryptUuid(toBan.get(1))));
}
}

View file

@ -58,7 +58,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
server = new TestZkGroupServer();
groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32)));
clientZkOperations = new ClientZkOperations(server.getServerPublicParams());
groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams);
groupOperations = new GroupsV2Operations(clientZkOperations, 1000).forGroup(groupSecretParams);
}
@Test
@ -157,7 +157,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
public void can_decrypt_member_removals_field4() {
UUID oldMember = UUID.randomUUID();
assertDecryption(groupOperations.createRemoveMembersChange(Collections.singleton(oldMember), false)
assertDecryption(groupOperations.createRemoveMembersChange(Collections.singleton(oldMember), false, Collections.emptyList())
.setRevision(10),
DecryptedGroupChange.newBuilder()
.setRevision(10)
@ -341,7 +341,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
public void can_decrypt_member_requests_refusals_field17() {
UUID newRequestingMember = UUID.randomUUID();
assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember), true)
assertDecryption(groupOperations.createRefuseGroupJoinRequest(Collections.singleton(newRequestingMember), true, Collections.emptyList())
.setRevision(10),
DecryptedGroupChange.newBuilder()
.setRevision(10)
@ -393,7 +393,7 @@ public final class GroupsV2Operations_decrypt_change_Test {
public void can_decrypt_member_bans_field22() {
UUID ban = UUID.randomUUID();
assertDecryption(groupOperations.createBanUuidsChange(Collections.singleton(ban), false)
assertDecryption(groupOperations.createBanUuidsChange(Collections.singleton(ban), false, Collections.emptyList())
.setRevision(13),
DecryptedGroupChange.newBuilder()
.setRevision(13)

View file

@ -28,7 +28,7 @@ public final class GroupsV2Operations_decrypt_groupJoinInfo_Test {
ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams());
GroupSecretParams groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32)));
groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams);
groupOperations = new GroupsV2Operations(clientZkOperations, 1000).forGroup(groupSecretParams);
}
/**

View file

@ -44,7 +44,7 @@ public final class GroupsV2Operations_decrypt_group_Test {
ClientZkOperations clientZkOperations = new ClientZkOperations(server.getServerPublicParams());
groupSecretParams = GroupSecretParams.deriveFromMasterKey(new GroupMasterKey(Util.getSecretBytes(32)));
groupOperations = new GroupsV2Operations(clientZkOperations).forGroup(groupSecretParams);
groupOperations = new GroupsV2Operations(clientZkOperations, 1000).forGroup(groupSecretParams);
}
/**