diff --git a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java
index 10d4280f41dcdc6634c535bd02d0ccf56abae2b3..6b149c13fa0269b7915e08ed4638c6e1e14c2a40 100644
--- a/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/PrivateGroupManagerTest.java
@@ -11,6 +11,7 @@ import org.briarproject.api.crypto.CryptoComponent;
 import org.briarproject.api.crypto.KeyPair;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.data.BdfList;
+import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.event.Event;
@@ -20,6 +21,7 @@ import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.lifecycle.LifecycleManager;
+import org.briarproject.api.privategroup.GroupMember;
 import org.briarproject.api.privategroup.GroupMessage;
 import org.briarproject.api.privategroup.GroupMessageFactory;
 import org.briarproject.api.privategroup.GroupMessageHeader;
@@ -59,6 +61,10 @@ import javax.inject.Inject;
 import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.TestUtils.getRandomBytes;
 import static org.briarproject.api.identity.Author.Status.VERIFIED;
+import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
+import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT;
+import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_YOU;
+import static org.briarproject.api.privategroup.Visibility.VISIBLE;
 import static org.briarproject.api.privategroup.invitation.GroupInvitationManager.CLIENT_ID;
 import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.api.sync.ValidationManager.State.INVALID;
@@ -69,13 +75,16 @@ import static org.junit.Assert.assertTrue;
 
 public class PrivateGroupManagerTest extends BriarIntegrationTest {
 
-	private LifecycleManager lifecycleManager0, lifecycleManager1;
-	private SyncSessionFactory sync0, sync1;
-	private PrivateGroupManager groupManager0, groupManager1;
-	private ContactManager contactManager0, contactManager1;
-	private ContactId contactId0, contactId1;
-	private IdentityManager identityManager0, identityManager1;
-	private LocalAuthor author0, author1;
+	private LifecycleManager lifecycleManager0, lifecycleManager1,
+			lifecycleManager2;
+	private SyncSessionFactory sync0, sync1, sync2;
+	private PrivateGroupManager groupManager0, groupManager1, groupManager2;
+	private ContactManager contactManager0, contactManager1, contactManager2;
+	private ContactId contactId01, contactId02, contactId1, contactId2;
+	private IdentityManager identityManager0, identityManager1,
+			identityManager2;
+	private LocalAuthor author0, author1, author2;
+	private DatabaseComponent db0, db1, db2;
 	private PrivateGroup privateGroup0;
 	private GroupId groupId0;
 
@@ -101,13 +110,14 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 	private final File testDir = TestUtils.getTestDirectory();
 	private final SecretKey master = TestUtils.getSecretKey();
 	private final int TIMEOUT = 15000;
+	private final String AUTHOR0 = "Author 0";
 	private final String AUTHOR1 = "Author 1";
 	private final String AUTHOR2 = "Author 2";
 
 	private static final Logger LOG =
 			Logger.getLogger(PrivateGroupManagerTest.class.getName());
 
-	private PrivateGroupManagerTestComponent t0, t1;
+	private PrivateGroupManagerTestComponent t0, t1, t2;
 
 	@Rule
 	public ExpectedException thrown = ExpectedException.none();
@@ -117,26 +127,36 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		PrivateGroupManagerTestComponent component =
 				DaggerPrivateGroupManagerTestComponent.builder().build();
 		component.inject(this);
-		injectEagerSingletons(component);
 
 		assertTrue(testDir.mkdirs());
-		File t0Dir = new File(testDir, AUTHOR1);
+		File t0Dir = new File(testDir, AUTHOR0);
 		t0 = DaggerPrivateGroupManagerTestComponent.builder()
 				.testDatabaseModule(new TestDatabaseModule(t0Dir)).build();
 		injectEagerSingletons(t0);
-		File t1Dir = new File(testDir, AUTHOR2);
+		File t1Dir = new File(testDir, AUTHOR1);
 		t1 = DaggerPrivateGroupManagerTestComponent.builder()
 				.testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
 		injectEagerSingletons(t1);
+		File t2Dir = new File(testDir, AUTHOR2);
+		t2 = DaggerPrivateGroupManagerTestComponent.builder()
+				.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
+		injectEagerSingletons(t2);
 
 		identityManager0 = t0.getIdentityManager();
 		identityManager1 = t1.getIdentityManager();
+		identityManager2 = t2.getIdentityManager();
 		contactManager0 = t0.getContactManager();
 		contactManager1 = t1.getContactManager();
+		contactManager2 = t2.getContactManager();
+		db0 = t0.getDatabaseComponent();
+		db1 = t1.getDatabaseComponent();
+		db2 = t2.getDatabaseComponent();
 		groupManager0 = t0.getPrivateGroupManager();
 		groupManager1 = t1.getPrivateGroupManager();
+		groupManager2 = t2.getPrivateGroupManager();
 		sync0 = t0.getSyncSessionFactory();
 		sync1 = t1.getSyncSessionFactory();
+		sync2 = t2.getSyncSessionFactory();
 
 		// initialize waiters fresh for each test
 		validationWaiter = new Waiter();
@@ -328,12 +348,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager0.getPreviousMsgId(groupId0));
 
 		// make group visible to 1
-		Transaction txn0 = t0.getDatabaseComponent().startTransaction(false);
-		t0.getDatabaseComponent()
-				.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
-						true);
-		t0.getDatabaseComponent().commitTransaction(txn0);
-		t0.getDatabaseComponent().endTransaction(txn0);
+		Transaction txn0 = db0.startTransaction(false);
+		db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
+		db0.commitTransaction(txn0);
+		db0.endTransaction(txn0);
 
 		// author1 joins privateGroup0 with wrong timestamp
 		joinTime = clock.currentTimeMillis();
@@ -353,12 +371,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager1.getPreviousMsgId(groupId0));
 
 		// make group visible to 0
-		Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
-		t1.getDatabaseComponent()
-				.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
-						true);
-		t1.getDatabaseComponent().commitTransaction(txn1);
-		t1.getDatabaseComponent().endTransaction(txn1);
+		Transaction txn1 = db1.startTransaction(false);
+		db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
+		db1.commitTransaction(txn1);
+		db1.endTransaction(txn1);
 
 		// sync join messages
 		sync0To1();
@@ -399,12 +415,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager0.getPreviousMsgId(groupId0));
 
 		// make group visible to 1
-		Transaction txn0 = t0.getDatabaseComponent().startTransaction(false);
-		t0.getDatabaseComponent()
-				.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
-						true);
-		t0.getDatabaseComponent().commitTransaction(txn0);
-		t0.getDatabaseComponent().endTransaction(txn0);
+		Transaction txn0 = db0.startTransaction(false);
+		db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
+		db0.commitTransaction(txn0);
+		db0.endTransaction(txn0);
 
 		// author1 joins privateGroup0 with wrong signature in join message
 		joinTime = clock.currentTimeMillis();
@@ -424,12 +438,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager1.getPreviousMsgId(groupId0));
 
 		// make group visible to 0
-		Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
-		t1.getDatabaseComponent()
-				.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
-						true);
-		t1.getDatabaseComponent().commitTransaction(txn1);
-		t1.getDatabaseComponent().endTransaction(txn1);
+		Transaction txn1 = db1.startTransaction(false);
+		db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
+		db1.commitTransaction(txn1);
+		db1.endTransaction(txn1);
 
 		// sync join messages
 		sync0To1();
@@ -445,6 +457,119 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		assertEquals(1, groupManager0.getHeaders(groupId0).size());
 	}
 
+	@Test
+	public void testGetMembers() throws Exception {
+		defaultInit();
+
+		Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
+		assertEquals(2, members0.size());
+		for (GroupMember m : members0) {
+			if (m.getAuthor().equals(author0)) {
+				assertEquals(VISIBLE, m.getVisibility());
+			} else {
+				assertEquals(author1, m.getAuthor());
+				assertEquals(VISIBLE, m.getVisibility());
+			}
+		}
+
+		Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
+		assertEquals(2, members1.size());
+		for (GroupMember m : members0) {
+			if (m.getAuthor().equals(author1)) {
+				assertEquals(VISIBLE, m.getVisibility());
+			} else {
+				assertEquals(author0, m.getAuthor());
+				assertEquals(VISIBLE, m.getVisibility());
+			}
+		}
+	}
+
+	@Test
+	public void testRevealingRelationships() throws Exception {
+		defaultInit();
+
+		// make group visible to 2
+		Transaction txn0 = db0.startTransaction(false);
+		db0.setVisibleToContact(txn0, contactId2, privateGroup0.getId(), true);
+		db0.commitTransaction(txn0);
+		db0.endTransaction(txn0);
+
+		// author2 joins privateGroup0
+		long joinTime = clock.currentTimeMillis();
+		long inviteTime = joinTime - 1;
+		Group invitationGroup = contactGroupFactory
+				.createContactGroup(CLIENT_ID, author0.getId(),
+						author2.getId());
+		BdfList toSign = BdfList.of(0, inviteTime, invitationGroup.getId(),
+				privateGroup0.getId());
+		byte[] creatorSignature =
+				clientHelper.sign(toSign, author0.getPrivateKey());
+		GroupMessage joinMsg2 = groupMessageFactory
+				.createJoinMessage(privateGroup0.getId(), joinTime, author2,
+						inviteTime, creatorSignature);
+		Transaction txn2 = db2.startTransaction(false);
+		groupManager2.addPrivateGroup(txn2, privateGroup0, joinMsg2);
+
+		// make group visible to 0
+		db2.setVisibleToContact(txn2, contactId01, privateGroup0.getId(), true);
+		db2.commitTransaction(txn2);
+		db2.endTransaction(txn2);
+
+		// sync join messages
+		deliverMessage(sync2, contactId2, sync0, contactId02, "2 to 0");
+		deliveryWaiter.await(TIMEOUT, 1);
+		deliverMessage(sync0, contactId02, sync2, contactId2, "0 to 2");
+		deliveryWaiter.await(TIMEOUT, 2);
+		sync0To1();
+		deliveryWaiter.await(TIMEOUT, 1);
+
+		// check that everybody sees everybody else as joined
+		Collection<GroupMember> members0 = groupManager0.getMembers(groupId0);
+		assertEquals(3, members0.size());
+		Collection<GroupMember> members1 = groupManager1.getMembers(groupId0);
+		assertEquals(3, members1.size());
+		Collection<GroupMember> members2 = groupManager2.getMembers(groupId0);
+		assertEquals(3, members2.size());
+
+		// assert that contact relationship is not revealed initially
+		for (GroupMember m : members1) {
+			if (m.getAuthor().equals(author2)) {
+				assertEquals(INVISIBLE, m.getVisibility());
+			}
+		}
+		for (GroupMember m : members2) {
+			if (m.getAuthor().equals(author1)) {
+				assertEquals(INVISIBLE, m.getVisibility());
+			}
+		}
+
+		// reveal contact relationship
+		Transaction txn1 = db1.startTransaction(false);
+		groupManager1
+				.relationshipRevealed(txn1, groupId0, author2.getId(), false);
+		db1.commitTransaction(txn1);
+		db1.endTransaction(txn1);
+		txn2 = db2.startTransaction(false);
+		groupManager2
+				.relationshipRevealed(txn2, groupId0, author1.getId(), true);
+		db2.commitTransaction(txn2);
+		db2.endTransaction(txn2);
+
+		// assert that contact relationship is now revealed properly
+		members1 = groupManager1.getMembers(groupId0);
+		for (GroupMember m : members1) {
+			if (m.getAuthor().equals(author2)) {
+				assertEquals(REVEALED_BY_YOU, m.getVisibility());
+			}
+		}
+		members2 = groupManager2.getMembers(groupId0);
+		for (GroupMember m : members2) {
+			if (m.getAuthor().equals(author1)) {
+				assertEquals(REVEALED_BY_CONTACT, m.getVisibility());
+			}
+		}
+	}
+
 	@After
 	public void tearDown() throws Exception {
 		stopLifecycles();
@@ -483,7 +608,7 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		byte[] publicKey0 = keyPair0.getPublic().getEncoded();
 		byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
 		author0 = authorFactory
-				.createLocalAuthor(AUTHOR1, publicKey0, privateKey0);
+				.createLocalAuthor(AUTHOR0, publicKey0, privateKey0);
 		identityManager0.registerLocalAuthor(author0);
 		privateGroup0 =
 				privateGroupFactory.createPrivateGroup("Testgroup", author0);
@@ -493,21 +618,34 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		byte[] publicKey1 = keyPair1.getPublic().getEncoded();
 		byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
 		author1 = authorFactory
-				.createLocalAuthor(AUTHOR2, publicKey1, privateKey1);
+				.createLocalAuthor(AUTHOR1, publicKey1, privateKey1);
 		identityManager1.registerLocalAuthor(author1);
+
+		KeyPair keyPair2 = crypto.generateSignatureKeyPair();
+		byte[] publicKey2 = keyPair2.getPublic().getEncoded();
+		byte[] privateKey2 = keyPair2.getPrivate().getEncoded();
+		author2 = authorFactory
+				.createLocalAuthor(AUTHOR2, publicKey2, privateKey2);
+		identityManager2.registerLocalAuthor(author2);
 	}
 
 	private void addDefaultContacts() throws DbException {
-		// sharer adds invitee as contact
-		contactId1 = contactManager0.addContact(author1,
-				author0.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
-		// invitee adds sharer back
-		contactId0 = contactManager1.addContact(author0,
-				author1.getId(), master, clock.currentTimeMillis(), true,
-				true, true
-		);
+		// creator adds invitee as contact
+		contactId1 = contactManager0
+				.addContact(author1, author0.getId(), master,
+						clock.currentTimeMillis(), true, true, true);
+		// invitee adds creator back
+		contactId01 = contactManager1
+				.addContact(author0, author1.getId(), master,
+						clock.currentTimeMillis(), true, true, true);
+		// creator adds invitee as contact
+		contactId2 = contactManager0
+				.addContact(author2, author0.getId(), master,
+						clock.currentTimeMillis(), true, true, true);
+		// invitee adds creator back
+		contactId02 = contactManager2
+				.addContact(author0, author2.getId(), master,
+						clock.currentTimeMillis(), true, true, true);
 	}
 
 	private void listenToEvents() {
@@ -515,6 +653,8 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		t0.getEventBus().addListener(listener0);
 		Listener listener1 = new Listener();
 		t1.getEventBus().addListener(listener1);
+		Listener listener2 = new Listener();
+		t2.getEventBus().addListener(listener2);
 	}
 
 	private void addGroup() throws Exception {
@@ -527,12 +667,10 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 				groupManager0.getPreviousMsgId(groupId0));
 
 		// make group visible to 1
-		Transaction txn0 = t0.getDatabaseComponent().startTransaction(false);
-		t0.getDatabaseComponent()
-				.setVisibleToContact(txn0, contactId1, privateGroup0.getId(),
-						true);
-		t0.getDatabaseComponent().commitTransaction(txn0);
-		t0.getDatabaseComponent().endTransaction(txn0);
+		Transaction txn0 = db0.startTransaction(false);
+		db0.setVisibleToContact(txn0, contactId1, privateGroup0.getId(), true);
+		db0.commitTransaction(txn0);
+		db0.endTransaction(txn0);
 
 		// author1 joins privateGroup0
 		joinTime = clock.currentTimeMillis();
@@ -547,17 +685,15 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		GroupMessage joinMsg1 = groupMessageFactory
 				.createJoinMessage(privateGroup0.getId(), joinTime, author1,
 						inviteTime, creatorSignature);
-		groupManager1.addPrivateGroup(privateGroup0, joinMsg1);
-		assertEquals(joinMsg1.getMessage().getId(),
-				groupManager1.getPreviousMsgId(groupId0));
+		Transaction txn1 = db1.startTransaction(false);
+		groupManager1.addPrivateGroup(txn1, privateGroup0, joinMsg1);
 
 		// make group visible to 0
-		Transaction txn1 = t1.getDatabaseComponent().startTransaction(false);
-		t1.getDatabaseComponent()
-				.setVisibleToContact(txn1, contactId0, privateGroup0.getId(),
-						true);
-		t1.getDatabaseComponent().commitTransaction(txn1);
-		t1.getDatabaseComponent().endTransaction(txn1);
+		db1.setVisibleToContact(txn1, contactId01, privateGroup0.getId(), true);
+		db1.commitTransaction(txn1);
+		db1.endTransaction(txn1);
+		assertEquals(joinMsg1.getMessage().getId(),
+				groupManager1.getPreviousMsgId(groupId0));
 
 		// sync join messages
 		sync0To1();
@@ -567,11 +703,11 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 	}
 
 	private void sync0To1() throws IOException, TimeoutException {
-		deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
+		deliverMessage(sync0, contactId01, sync1, contactId1, "0 to 1");
 	}
 
 	private void sync1To0() throws IOException, TimeoutException {
-		deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
+		deliverMessage(sync1, contactId1, sync0, contactId01, "1 to 0");
 	}
 
 	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
@@ -600,18 +736,23 @@ public class PrivateGroupManagerTest extends BriarIntegrationTest {
 		// Start the lifecycle manager and wait for it to finish
 		lifecycleManager0 = t0.getLifecycleManager();
 		lifecycleManager1 = t1.getLifecycleManager();
-		lifecycleManager0.startServices(AUTHOR1);
-		lifecycleManager1.startServices(AUTHOR2);
+		lifecycleManager2 = t2.getLifecycleManager();
+		lifecycleManager0.startServices(AUTHOR0);
+		lifecycleManager1.startServices(AUTHOR1);
+		lifecycleManager2.startServices(AUTHOR2);
 		lifecycleManager0.waitForStartup();
 		lifecycleManager1.waitForStartup();
+		lifecycleManager2.waitForStartup();
 	}
 
 	private void stopLifecycles() throws InterruptedException {
 		// Clean up
 		lifecycleManager0.stopServices();
 		lifecycleManager1.stopServices();
+		lifecycleManager2.stopServices();
 		lifecycleManager0.waitForShutdown();
 		lifecycleManager1.waitForShutdown();
+		lifecycleManager2.waitForShutdown();
 	}
 
 	private void injectEagerSingletons(
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java
index e59f6cdacab3fef325737597d2c1a59d105aa5b9..75a194d3246ddbba327f39d0c64d7d1c2dd45b72 100644
--- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupActivity.java
@@ -105,7 +105,7 @@ public class GroupActivity extends
 		ActionBar actionBar = getSupportActionBar();
 		if (actionBar != null) {
 			actionBar.setSubtitle(getString(R.string.groups_created_by,
-					group.getAuthor().getName()));
+					group.getCreator().getName()));
 		}
 		controller.isCreator(group,
 				new UiResultExceptionHandler<Boolean, DbException>(this) {
diff --git a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
index 89de8033149d97050882402892d25185e36cd6d8..e0ba5d3d89fcd318a334117813d2ccebe90de489 100644
--- a/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/privategroup/conversation/GroupControllerImpl.java
@@ -196,7 +196,7 @@ public class GroupControllerImpl extends
 				try {
 					LocalAuthor author = identityManager.getLocalAuthor();
 					boolean isCreator =
-							author.getId().equals(group.getAuthor().getId());
+							author.getId().equals(group.getCreator().getId());
 					handler.onResult(isCreator);
 				} catch (DbException e) {
 					if (LOG.isLoggable(WARNING))
diff --git a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java
index 31c6458a654bc0fbbd409e2e1810b85a36836aab..ded8051817fd42bedf7ca52bcefd95be37e85ac6 100644
--- a/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java
+++ b/briar-android/src/org/briarproject/android/privategroup/list/GroupItem.java
@@ -43,7 +43,7 @@ class GroupItem {
 	}
 
 	Author getCreator() {
-		return privateGroup.getAuthor();
+		return privateGroup.getCreator();
 	}
 
 	String getName() {
diff --git a/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java b/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java
index ef0c73d91f2e1a5cacccea458d6f1bbe0a44c92f..9ff87a510b2d04fc92af80ae264d9121ef183265 100644
--- a/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java
+++ b/briar-android/src/org/briarproject/android/privategroup/memberlist/MemberListItem.java
@@ -7,6 +7,8 @@ import org.briarproject.api.privategroup.GroupMember;
 
 import javax.annotation.concurrent.Immutable;
 
+import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
+
 @Immutable
 @NotNullByDefault
 class MemberListItem {
@@ -17,7 +19,7 @@ class MemberListItem {
 
 	public MemberListItem(GroupMember groupMember) {
 		this.member = groupMember.getAuthor();
-		this.sharing = groupMember.isShared();
+		this.sharing = groupMember.getVisibility() != INVISIBLE; // TODO #732
 		this.status = groupMember.getStatus();
 	}
 
diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMember.java b/briar-api/src/org/briarproject/api/privategroup/GroupMember.java
index e433aa792b30ad07efc8d1944122d856fa4f0bcd..2638dfe706b6e37b4006e45fb6f335d776825714 100644
--- a/briar-api/src/org/briarproject/api/privategroup/GroupMember.java
+++ b/briar-api/src/org/briarproject/api/privategroup/GroupMember.java
@@ -12,12 +12,12 @@ public class GroupMember {
 
 	private final Author author;
 	private final Status status;
-	private final boolean shared;
+	private final Visibility visibility;
 
-	public GroupMember(Author author, Status status, boolean shared) {
+	public GroupMember(Author author, Status status, Visibility visibility) {
 		this.author = author;
 		this.status = status;
-		this.shared = shared;
+		this.visibility = visibility;
 	}
 
 	public Author getAuthor() {
@@ -28,8 +28,8 @@ public class GroupMember {
 		return status;
 	}
 
-	public boolean isShared() {
-		return shared;
+	public Visibility getVisibility() {
+		return visibility;
 	}
 
 }
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
index ab8d520590072fc3bd2e6328a565f92173a47ab9..3095d2abbac6faf80d053fe42ce824a7d750f804 100644
--- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroup.java
@@ -12,20 +12,22 @@ import javax.annotation.concurrent.Immutable;
 @NotNullByDefault
 public class PrivateGroup extends NamedGroup implements Shareable {
 
-	private final Author author;
+	private final Author creator;
 
-	public PrivateGroup(Group group, String name, Author author, byte[] salt) {
+	public PrivateGroup(Group group, String name, Author creator, byte[] salt) {
 		super(group, name, salt);
-		this.author = author;
+		this.creator = creator;
 	}
 
-	public Author getAuthor() {
-		return author;
+	public Author getCreator() {
+		return creator;
 	}
 
 	@Override
 	public boolean equals(Object o) {
-		return o instanceof PrivateGroup && super.equals(o);
+		return o instanceof PrivateGroup &&
+				creator.equals(((PrivateGroup) o).getCreator()) &&
+				super.equals(o);
 	}
 
 }
diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
index 4a96a843b999537f809ca6fa99a33c69aab5cdab..f0197e0969ec7ca0f2da62ed77b54937f17166e5 100644
--- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
+++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java
@@ -1,9 +1,11 @@
 package org.briarproject.api.privategroup;
 
+import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.MessageTracker;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.nullsafety.NotNullByDefault;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
@@ -20,16 +22,16 @@ public interface PrivateGroupManager extends MessageTracker {
 	ClientId CLIENT_ID = new ClientId("org.briarproject.briar.privategroup");
 
 	/**
-	 * Adds a new private group and joins it.
+	 * Adds a new private group and joins it as the creator.
 	 *
 	 * @param group        The private group to add
-	 * @param joinMsg      The new member's join message
+	 * @param joinMsg      The creators's join message
 	 */
 	void addPrivateGroup(PrivateGroup group, GroupMessage joinMsg)
 			throws DbException;
 
 	/**
-	 * Adds a new private group and joins it.
+	 * Adds a new private group and joins it as a member.
 	 *
 	 * @param group        The private group to add
 	 * @param joinMsg      The new member's join message
@@ -103,6 +105,17 @@ public interface PrivateGroupManager extends MessageTracker {
 	 */
 	boolean isMember(Transaction txn, GroupId g, Author a) throws DbException;
 
+	/**
+	 * This method needs to be called when a contact relationship
+	 * has been revealed between you and the Author with AuthorId a
+	 * in the Group identified by the GroupId g.
+	 *
+	 * @param byContact true if the remote contact has revealed
+	 *                     the relationship first. Otherwise false.
+	 */
+	void relationshipRevealed(Transaction txn, GroupId g, AuthorId a,
+			boolean byContact) throws FormatException, DbException;
+
 	/**
 	 * Registers a hook to be called when members are added
 	 * or groups are removed.
diff --git a/briar-api/src/org/briarproject/api/privategroup/Visibility.java b/briar-api/src/org/briarproject/api/privategroup/Visibility.java
new file mode 100644
index 0000000000000000000000000000000000000000..9c535e86b8346c9681d8ce03e449a1e6a266edf4
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/privategroup/Visibility.java
@@ -0,0 +1,25 @@
+package org.briarproject.api.privategroup;
+
+public enum Visibility {
+
+	INVISIBLE(0),
+	VISIBLE(1),
+	REVEALED_BY_YOU(2),
+	REVEALED_BY_CONTACT(3);
+
+	int value;
+
+	Visibility(int value) {
+		this.value = value;
+	}
+
+	public static Visibility valueOf(int value) {
+		for (Visibility v : values()) if (v.value == value) return v;
+		throw new IllegalArgumentException();
+	}
+
+	public int getInt() {
+		return value;
+	}
+
+}
diff --git a/briar-core/src/org/briarproject/privategroup/GroupConstants.java b/briar-core/src/org/briarproject/privategroup/GroupConstants.java
index 8bc82be42b4d6146282ec1d90496a7df1c35d349..14d9fb22efc46d29859633e777d11412b8cfcfab 100644
--- a/briar-core/src/org/briarproject/privategroup/GroupConstants.java
+++ b/briar-core/src/org/briarproject/privategroup/GroupConstants.java
@@ -9,13 +9,15 @@ interface Constants {
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_READ = MSG_KEY_READ;
 	String KEY_PARENT_MSG_ID = "parentMsgId";
-	String KEY_NEW_MEMBER_MSG_ID = "newMemberMsgId";
 	String KEY_PREVIOUS_MSG_ID = "previousMsgId";
 	String KEY_MEMBER_ID = "memberId";
 	String KEY_MEMBER_NAME = "memberName";
 	String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey";
 
-	String KEY_MEMBERS = "members";
-	String KEY_DISSOLVED = "dissolved";
+	String GROUP_KEY_MEMBERS = "members";
+	String GROUP_KEY_OUR_GROUP = "ourGroup";
+	String GROUP_KEY_CREATOR_ID = "creatorId";
+	String GROUP_KEY_DISSOLVED = "dissolved";
+	String GROUP_KEY_VISIBILITY = "visibility";
 
 }
diff --git a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
index dda084d2e1e4fa7c13495468ab036dcf15692ee2..6b9a59dae9d7f1788fb5c67d391a9835b8dcba4b 100644
--- a/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
+++ b/briar-core/src/org/briarproject/privategroup/GroupMessageValidator.java
@@ -98,7 +98,7 @@ class GroupMessageValidator extends BdfMessageValidator {
 		PrivateGroup pg = privateGroupFactory.parsePrivateGroup(g);
 
 		// invite is null if the member is the creator of the private group
-		Author creator = pg.getAuthor();
+		Author creator = pg.getCreator();
 		BdfList invite = body.getOptionalList(3);
 		if (invite == null) {
 			if (!member.equals(creator))
diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
index b6117f65f198a4029d1eb8021069c031cacf3366..a407f1fcf3b1afee01f30a260230ba30647ea08a 100644
--- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java
@@ -2,8 +2,7 @@ package org.briarproject.privategroup;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.contact.Contact;
-import org.briarproject.api.contact.ContactId;
+import org.briarproject.api.clients.ProtocolStateException;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.data.BdfList;
@@ -27,6 +26,7 @@ import org.briarproject.api.privategroup.MessageType;
 import org.briarproject.api.privategroup.PrivateGroup;
 import org.briarproject.api.privategroup.PrivateGroupFactory;
 import org.briarproject.api.privategroup.PrivateGroupManager;
+import org.briarproject.api.privategroup.Visibility;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
@@ -48,12 +48,17 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import javax.inject.Inject;
 
 import static org.briarproject.api.identity.Author.Status.OURSELVES;
-import static org.briarproject.api.identity.Author.Status.UNVERIFIED;
-import static org.briarproject.api.identity.Author.Status.VERIFIED;
 import static org.briarproject.api.privategroup.MessageType.JOIN;
 import static org.briarproject.api.privategroup.MessageType.POST;
-import static org.briarproject.privategroup.Constants.KEY_DISSOLVED;
-import static org.briarproject.privategroup.Constants.KEY_MEMBERS;
+import static org.briarproject.api.privategroup.Visibility.INVISIBLE;
+import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_CONTACT;
+import static org.briarproject.api.privategroup.Visibility.REVEALED_BY_YOU;
+import static org.briarproject.api.privategroup.Visibility.VISIBLE;
+import static org.briarproject.privategroup.Constants.GROUP_KEY_CREATOR_ID;
+import static org.briarproject.privategroup.Constants.GROUP_KEY_DISSOLVED;
+import static org.briarproject.privategroup.Constants.GROUP_KEY_MEMBERS;
+import static org.briarproject.privategroup.Constants.GROUP_KEY_OUR_GROUP;
+import static org.briarproject.privategroup.Constants.GROUP_KEY_VISIBILITY;
 import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID;
 import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME;
 import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY;
@@ -88,7 +93,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 			throws DbException {
 		Transaction txn = db.startTransaction(false);
 		try {
-			addPrivateGroup(txn, group, joinMsg);
+			addPrivateGroup(txn, group, joinMsg, true);
 			db.commitTransaction(txn);
 		} finally {
 			db.endTransaction(txn);
@@ -98,11 +103,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	@Override
 	public void addPrivateGroup(Transaction txn, PrivateGroup group,
 			GroupMessage joinMsg) throws DbException {
+		addPrivateGroup(txn, group, joinMsg, false);
+	}
+
+	private void addPrivateGroup(Transaction txn, PrivateGroup group,
+			GroupMessage joinMsg, boolean creator) throws DbException {
 		try {
 			db.addGroup(txn, group.getGroup());
+			AuthorId creatorId = joinMsg.getMember().getId();
 			BdfDictionary meta = BdfDictionary.of(
-					new BdfEntry(KEY_MEMBERS, new BdfList()),
-					new BdfEntry(KEY_DISSOLVED, false)
+					new BdfEntry(GROUP_KEY_MEMBERS, new BdfList()),
+					new BdfEntry(GROUP_KEY_CREATOR_ID, creatorId),
+					new BdfEntry(GROUP_KEY_OUR_GROUP, creator),
+					new BdfEntry(GROUP_KEY_DISSOLVED, false)
 			);
 			clientHelper.mergeGroupMetadata(txn, group.getId(), meta);
 			joinPrivateGroup(txn, joinMsg);
@@ -118,7 +131,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		addMessageMetadata(meta, m, true);
 		clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 		trackOutgoingMessage(txn, m.getMessage());
-		addMember(txn, m.getMessage().getGroupId(), m.getMember());
+		addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
 		setPreviousMsgId(txn, m.getMessage().getGroupId(),
 				m.getMessage().getId());
 	}
@@ -171,7 +184,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	public void markGroupDissolved(Transaction txn, GroupId g)
 			throws DbException {
 		BdfDictionary meta = BdfDictionary.of(
-				new BdfEntry(KEY_DISSOLVED, true)
+				new BdfEntry(GROUP_KEY_DISSOLVED, true)
 		);
 		try {
 			clientHelper.mergeGroupMetadata(txn, g, meta);
@@ -283,7 +296,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	public boolean isDissolved(GroupId g) throws DbException {
 		try {
 			BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g);
-			return meta.getBoolean(KEY_DISSOLVED);
+			return meta.getBoolean(GROUP_KEY_DISSOLVED);
 		} catch (FormatException e) {
 			throw new DbException(e);
 		}
@@ -376,18 +389,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		Transaction txn = db.startTransaction(true);
 		try {
 			Collection<GroupMember> members = new ArrayList<GroupMember>();
-			Collection<Author> authors = getMembers(txn, g);
-			for (Author a : authors) {
-				Status status = identityManager.getAuthorStatus(txn, a.getId());
-				boolean shared = false;
-				if (status == VERIFIED || status == UNVERIFIED) {
-					Collection<Contact> contacts =
-							db.getContactsByAuthorId(txn, a.getId());
-					if (contacts.size() != 1) throw new DbException();
-					ContactId c = contacts.iterator().next().getId();
-					shared = db.isVisibleToContact(txn, c, g);
-				}
-				members.add(new GroupMember(a, status, shared));
+			Map<Author, Visibility> authors = getMembers(txn, g);
+			for (Entry<Author, Visibility> m : authors.entrySet()) {
+				Status status = identityManager
+						.getAuthorStatus(txn, m.getKey().getId());
+				members.add(new GroupMember(m.getKey(), status, m.getValue()));
 			}
 			db.commitTransaction(txn);
 			return members;
@@ -396,17 +402,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		}
 	}
 
-	private Collection<Author> getMembers(Transaction txn, GroupId g)
+	private Map<Author, Visibility> getMembers(Transaction txn, GroupId g)
 			throws DbException {
 		try {
-			Collection<Author> members = new ArrayList<Author>();
 			BdfDictionary meta =
 					clientHelper.getGroupMetadataAsDictionary(txn, g);
-			BdfList list = meta.getList(KEY_MEMBERS);
+			BdfList list = meta.getList(GROUP_KEY_MEMBERS);
+			Map<Author, Visibility> members =
+					new HashMap<Author, Visibility>(list.size());
 			for (Object o : list) {
 				BdfDictionary d = (BdfDictionary) o;
 				Author member = getAuthor(d);
-				members.add(member);
+				Visibility v = getVisibility(d);
+				members.put(member, v);
 			}
 			return members;
 		} catch (FormatException e) {
@@ -417,12 +425,34 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	@Override
 	public boolean isMember(Transaction txn, GroupId g, Author a)
 			throws DbException {
-		for (Author member : getMembers(txn, g)) {
+		for (Author member : getMembers(txn, g).keySet()) {
 			if (member.equals(a)) return true;
 		}
 		return false;
 	}
 
+	@Override
+	public void relationshipRevealed(Transaction txn, GroupId g, AuthorId a,
+			boolean byContact) throws FormatException, DbException {
+		BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g);
+		BdfList members = meta.getList(GROUP_KEY_MEMBERS);
+		boolean foundMember = false;
+		for (Object o : members) {
+			BdfDictionary d = (BdfDictionary) o;
+			AuthorId memberId = new AuthorId(d.getRaw(KEY_MEMBER_ID));
+			if (a.equals(memberId)) {
+				foundMember = true;
+				Visibility vOld = getVisibility(d);
+				if (vOld != INVISIBLE) throw new ProtocolStateException();
+				Visibility vNew =
+						byContact ? REVEALED_BY_CONTACT : REVEALED_BY_YOU;
+				d.put(GROUP_KEY_VISIBILITY, vNew.getInt());
+			}
+		}
+		if (!foundMember) throw new ProtocolStateException();
+		clientHelper.mergeGroupMetadata(txn, g, meta);
+	}
+
 	@Override
 	public void registerPrivateGroupHook(PrivateGroupHook hook) {
 		hooks.add(hook);
@@ -432,47 +462,14 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 	protected boolean incomingMessage(Transaction txn, Message m, BdfList body,
 			BdfDictionary meta) throws DbException, FormatException {
 
-		long timestamp = meta.getLong(KEY_TIMESTAMP);
 		MessageType type =
 				MessageType.valueOf(meta.getLong(KEY_TYPE).intValue());
 		switch (type) {
 			case JOIN:
-				addMember(txn, m.getGroupId(), getAuthor(meta));
-				trackIncomingMessage(txn, m);
-				attachGroupMessageAddedEvent(txn, m, meta, false);
+				handleJoinMessage(txn, m, meta);
 				return true;
 			case POST:
-				// timestamp must be greater than the timestamps of parent post
-				byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID);
-				if (parentIdBytes != null) {
-					MessageId parentId = new MessageId(parentIdBytes);
-					BdfDictionary parentMeta = clientHelper
-							.getMessageMetadataAsDictionary(txn, parentId);
-					if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP))
-						throw new FormatException();
-					MessageType parentType = MessageType
-							.valueOf(parentMeta.getLong(KEY_TYPE).intValue());
-					if (parentType != POST)
-						throw new FormatException();
-				}
-				// and the member's previous message
-				byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID);
-				MessageId previousMsgId = new MessageId(previousMsgIdBytes);
-				BdfDictionary previousMeta = clientHelper
-						.getMessageMetadataAsDictionary(txn, previousMsgId);
-				if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP))
-					throw new FormatException();
-				// previous message must be from same member
-				if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID),
-						previousMeta.getRaw(KEY_MEMBER_ID)))
-					throw new FormatException();
-				// previous message must be a POST or JOIN
-				MessageType previousType = MessageType
-						.valueOf(previousMeta.getLong(KEY_TYPE).intValue());
-				if (previousType != JOIN && previousType != POST)
-					throw new FormatException();
-				trackIncomingMessage(txn, m);
-				attachGroupMessageAddedEvent(txn, m, meta, false);
+				handleGroupMessage(txn, m, meta);
 				return true;
 			default:
 				// the validator should only let valid types pass
@@ -480,6 +477,63 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		}
 	}
 
+	private void handleJoinMessage(Transaction txn, Message m,
+			BdfDictionary meta) throws FormatException, DbException {
+		// find out if contact relationship is visible and then add new member
+		Author member = getAuthor(meta);
+		BdfDictionary groupMeta = clientHelper
+				.getGroupMetadataAsDictionary(txn, m.getGroupId());
+		boolean ourGroup = groupMeta.getBoolean(GROUP_KEY_OUR_GROUP);
+		Visibility v = VISIBLE;
+		if (!ourGroup) {
+			AuthorId creatorId = new AuthorId(
+					groupMeta.getRaw(GROUP_KEY_CREATOR_ID));
+			if (!creatorId.equals(member.getId()))
+				v = INVISIBLE;
+		}
+		addMember(txn, m.getGroupId(), member, v);
+		// track message and broadcast event
+		trackIncomingMessage(txn, m);
+		attachGroupMessageAddedEvent(txn, m, meta, false);
+	}
+
+	private void handleGroupMessage(Transaction txn, Message m,
+			BdfDictionary meta) throws FormatException, DbException {
+		// timestamp must be greater than the timestamps of parent post
+		long timestamp = meta.getLong(KEY_TIMESTAMP);
+		byte[] parentIdBytes = meta.getOptionalRaw(KEY_PARENT_MSG_ID);
+		if (parentIdBytes != null) {
+			MessageId parentId = new MessageId(parentIdBytes);
+			BdfDictionary parentMeta = clientHelper
+					.getMessageMetadataAsDictionary(txn, parentId);
+			if (timestamp <= parentMeta.getLong(KEY_TIMESTAMP))
+				throw new FormatException();
+			MessageType parentType = MessageType
+					.valueOf(parentMeta.getLong(KEY_TYPE).intValue());
+			if (parentType != POST)
+				throw new FormatException();
+		}
+		// and the member's previous message
+		byte[] previousMsgIdBytes = meta.getRaw(KEY_PREVIOUS_MSG_ID);
+		MessageId previousMsgId = new MessageId(previousMsgIdBytes);
+		BdfDictionary previousMeta = clientHelper
+				.getMessageMetadataAsDictionary(txn, previousMsgId);
+		if (timestamp <= previousMeta.getLong(KEY_TIMESTAMP))
+			throw new FormatException();
+		// previous message must be from same member
+		if (!Arrays.equals(meta.getRaw(KEY_MEMBER_ID),
+				previousMeta.getRaw(KEY_MEMBER_ID)))
+			throw new FormatException();
+		// previous message must be a POST or JOIN
+		MessageType previousType = MessageType
+				.valueOf(previousMeta.getLong(KEY_TYPE).intValue());
+		if (previousType != JOIN && previousType != POST)
+			throw new FormatException();
+		// track message and broadcast event
+		trackIncomingMessage(txn, m);
+		attachGroupMessageAddedEvent(txn, m, meta, false);
+	}
+
 	private void attachGroupMessageAddedEvent(Transaction txn, Message m,
 			BdfDictionary meta, boolean local)
 			throws DbException, FormatException {
@@ -489,15 +543,16 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		txn.attach(e);
 	}
 
-	private void addMember(Transaction txn, GroupId g, Author a)
+	private void addMember(Transaction txn, GroupId g, Author a, Visibility v)
 			throws DbException, FormatException {
 
 		BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g);
-		BdfList members = meta.getList(KEY_MEMBERS);
+		BdfList members = meta.getList(GROUP_KEY_MEMBERS);
 		members.add(BdfDictionary.of(
 				new BdfEntry(KEY_MEMBER_ID, a.getId()),
 				new BdfEntry(KEY_MEMBER_NAME, a.getName()),
-				new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey())
+				new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()),
+				new BdfEntry(GROUP_KEY_VISIBILITY, v.getInt())
 		));
 		clientHelper.mergeGroupMetadata(txn, g, meta);
 		for (PrivateGroupHook hook : hooks) {
@@ -512,4 +567,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements
 		return new Author(authorId, name, publicKey);
 	}
 
+	private Visibility getVisibility(BdfDictionary meta)
+			throws FormatException {
+		return Visibility
+				.valueOf(meta.getLong(GROUP_KEY_VISIBILITY).intValue());
+	}
+
 }
diff --git a/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java
index b3048c7779a76b9e878f5cbe0037fb3769ace04c..c62e4831d6ee3d92b3a0b5707a6f88e2bf6a444b 100644
--- a/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java
+++ b/briar-core/src/org/briarproject/privategroup/invitation/AbstractProtocolEngine.java
@@ -105,7 +105,7 @@ abstract class AbstractProtocolEngine<S extends Session>
 		}
 		Message m = messageEncoder.encodeInviteMessage(
 				session.getContactGroupId(), privateGroup.getId(),
-				timestamp, privateGroup.getName(), privateGroup.getAuthor(),
+				timestamp, privateGroup.getName(), privateGroup.getCreator(),
 				privateGroup.getSalt(), message, signature);
 		sendMessage(txn, m, INVITE, privateGroup.getId(), true);
 		return m;