diff --git a/briar-android/AndroidManifest.xml b/briar-android/AndroidManifest.xml
index fc8fcd5289bfedca1eb5bb9377338417d8ce7865..ad48f4aea19fe1e6bdaff23e45933a0979e636ba 100644
--- a/briar-android/AndroidManifest.xml
+++ b/briar-android/AndroidManifest.xml
@@ -42,15 +42,15 @@
 		</activity>
 		<activity
 			android:name=".android.groups.GroupActivity"
-			android:label="@string/groups_title" >
+			android:label="@string/app_name" >
 		</activity>
 		<activity
 			android:name=".android.groups.GroupListActivity"
-			android:label="@string/groups_title" >
+			android:label="@string/app_name" >
 		</activity>
 		<activity
 			android:name=".android.groups.ReadGroupMessageActivity"
-			android:label="@string/groups_title" >
+			android:label="@string/app_name" >
 		</activity>
 		<activity
 			android:name=".android.groups.WriteGroupMessageActivity"
diff --git a/briar-android/res/drawable-hdpi/social_blog.png b/briar-android/res/drawable-hdpi/social_blog.png
new file mode 100644
index 0000000000000000000000000000000000000000..dfafb709b7322d9a0ff679c975e1835ec1499966
Binary files /dev/null and b/briar-android/res/drawable-hdpi/social_blog.png differ
diff --git a/briar-android/res/drawable-hdpi/social_new_blog.png b/briar-android/res/drawable-hdpi/social_new_blog.png
new file mode 100644
index 0000000000000000000000000000000000000000..ad2c5f31a36616193928b7cb7239a15f6b9b52ed
Binary files /dev/null and b/briar-android/res/drawable-hdpi/social_new_blog.png differ
diff --git a/briar-android/res/drawable-mdpi/social_blog.png b/briar-android/res/drawable-mdpi/social_blog.png
new file mode 100644
index 0000000000000000000000000000000000000000..22a330fec002129d3251a169101e84b3ef9b7be7
Binary files /dev/null and b/briar-android/res/drawable-mdpi/social_blog.png differ
diff --git a/briar-android/res/drawable-mdpi/social_new_blog.png b/briar-android/res/drawable-mdpi/social_new_blog.png
new file mode 100644
index 0000000000000000000000000000000000000000..039e5f8537e4480d6ce1e247862cd8a18908e452
Binary files /dev/null and b/briar-android/res/drawable-mdpi/social_new_blog.png differ
diff --git a/briar-android/res/drawable-xhdpi/social_blog.png b/briar-android/res/drawable-xhdpi/social_blog.png
new file mode 100644
index 0000000000000000000000000000000000000000..951a5d3f67822aadfe1d1401e4d348e929206d07
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/social_blog.png differ
diff --git a/briar-android/res/drawable-xhdpi/social_new_blog.png b/briar-android/res/drawable-xhdpi/social_new_blog.png
new file mode 100644
index 0000000000000000000000000000000000000000..ee25de145dbcde7cb2f4ce316103adf866ee287e
Binary files /dev/null and b/briar-android/res/drawable-xhdpi/social_new_blog.png differ
diff --git a/briar-android/res/values/color.xml b/briar-android/res/values/color.xml
index 6252b617f5917071486b1c494cfd1ca027f4a7a8..986454bcf21d472f43f734c9f89b111700953063 100644
--- a/briar-android/res/values/color.xml
+++ b/briar-android/res/values/color.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <resources>
+    <color name="home_screen_background">#FFFFFF</color>
     <color name="content_background">#FFFFFF</color>
     <color name="unread_background">#FFFFFF</color>
 	<color name="horizontal_border">#CCCCCC</color>
diff --git a/briar-android/res/values/strings.xml b/briar-android/res/values/strings.xml
index 14258e04719804e036265ed5c8b05d5a2e558144..5881c29be047db3d0d2085ac01b2ab7d62306ab9 100644
--- a/briar-android/res/values/strings.xml
+++ b/briar-android/res/values/strings.xml
@@ -44,7 +44,8 @@
 	<string name="format_to">To: %1$s</string>
 	<string name="compose_message_title">New Message</string>
 	<string name="to">To:</string>
-	<string name="groups_title">Groups</string>
 	<string name="anonymous">(Anonymous)</string>
+	<string name="groups_title">Groups</string>
 	<string name="compose_group_title">New Post</string>
+	<string name="blogs_title">Blogs</string>
 </resources>
diff --git a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
index 02cea07efb922402829581a0e8f09f40b23bfc2e..3cf51590515e8866706ef598cb4c082f02d9d1ac 100644
--- a/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
+++ b/briar-android/src/net/sf/briar/android/HomeScreenActivity.java
@@ -89,8 +89,12 @@ public class HomeScreenActivity extends BriarActivity {
 			groupsButton.setText(R.string.groups_button);
 			groupsButton.setOnClickListener(new OnClickListener() {
 				public void onClick(View view) {
-					startActivity(new Intent(HomeScreenActivity.this,
-							GroupListActivity.class));
+					Intent i = new Intent(HomeScreenActivity.this,
+							GroupListActivity.class);
+					i.putExtra("net.sf.briar.RESTRICTED", false);
+					i.putExtra("net.sf.briar.TITLE",
+							getResources().getString(R.string.groups_title));
+					startActivity(i);
 				}
 			});
 			buttons.add(groupsButton);
@@ -99,11 +103,16 @@ public class HomeScreenActivity extends BriarActivity {
 			blogsButton.setLayoutParams(matchParent);
 			blogsButton.setBackgroundResource(0);
 			blogsButton.setCompoundDrawablesWithIntrinsicBounds(0,
-					R.drawable.social_share, 0, 0);
+					R.drawable.social_blog, 0, 0);
 			blogsButton.setText(R.string.blogs_button);
 			blogsButton.setOnClickListener(new OnClickListener() {
 				public void onClick(View view) {
-					// FIXME: Hook this button up to an activity
+					Intent i = new Intent(HomeScreenActivity.this,
+							GroupListActivity.class);
+					i.putExtra("net.sf.briar.RESTRICTED", true);
+					i.putExtra("net.sf.briar.TITLE",
+							getResources().getString(R.string.blogs_title));
+					startActivity(i);
 				}
 			});
 			buttons.add(blogsButton);
@@ -138,6 +147,8 @@ public class HomeScreenActivity extends BriarActivity {
 			grid.setLayoutParams(matchParent);
 			grid.setGravity(CENTER);
 			grid.setPadding(5, 5, 5, 5);
+			grid.setBackgroundColor(getResources().getColor(
+					R.color.home_screen_background));
 			grid.setNumColumns(2);
 			grid.setAdapter(new BaseAdapter() {
 
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
index deff171b85a52ce829bbc2effb32f5732001cc69..43af87cf9d2afbf06942ff05c3f4b6cc4eb8a839 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupActivity.java
@@ -51,6 +51,7 @@ OnClickListener, OnItemClickListener {
 	private final BriarServiceConnection serviceConnection =
 			new BriarServiceConnection();
 
+	private boolean restricted = false;
 	private String groupName = null;
 	private GroupAdapter adapter = null;
 	private ListView list = null;
@@ -65,6 +66,7 @@ OnClickListener, OnItemClickListener {
 		super.onCreate(null);
 
 		Intent i = getIntent();
+		restricted = i.getBooleanExtra("net.sf.briar.RESTRICTED", false);
 		byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
 		if(id == null) throw new IllegalStateException();
 		groupId = new GroupId(id);
@@ -194,7 +196,8 @@ OnClickListener, OnItemClickListener {
 
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
-			if(((GroupMessageAddedEvent) e).getGroupId().equals(groupId)) {
+			GroupMessageAddedEvent g = (GroupMessageAddedEvent) e;
+			if(g.getGroup().getId().equals(groupId)) {
 				if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
 				loadHeaders();
 			}
@@ -205,7 +208,8 @@ OnClickListener, OnItemClickListener {
 			if(LOG.isLoggable(INFO)) LOG.info("Rating changed, reloading");
 			loadHeaders();
 		} else if(e instanceof SubscriptionRemovedEvent) {
-			if(((SubscriptionRemovedEvent) e).getGroupId().equals(groupId)) {
+			SubscriptionRemovedEvent s = (SubscriptionRemovedEvent) e;
+			if(s.getGroup().getId().equals(groupId)) {
 				if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
 				finishOnUiThread();
 			}
@@ -214,6 +218,7 @@ OnClickListener, OnItemClickListener {
 
 	public void onClick(View view) {
 		Intent i = new Intent(this, WriteGroupMessageActivity.class);
+		i.putExtra("net.sf.briar.RESTRICTED", restricted);
 		i.putExtra("net.sf.briar.GROUP_ID", groupId.getBytes());
 		startActivity(i);
 	}
diff --git a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
index f1885f61021b77860ade106310b6b5266c5e91c8..70d797bb358e958e1854c008ee910c37413f2b47 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -1,6 +1,8 @@
 package net.sf.briar.android.groups;
 
+import static android.view.Gravity.CENTER;
 import static android.view.Gravity.CENTER_HORIZONTAL;
+import static android.widget.LinearLayout.HORIZONTAL;
 import static android.widget.LinearLayout.VERTICAL;
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
@@ -26,6 +28,7 @@ import net.sf.briar.android.BriarService;
 import net.sf.briar.android.BriarService.BriarServiceConnection;
 import net.sf.briar.android.widgets.CommonLayoutParams;
 import net.sf.briar.android.widgets.HorizontalBorder;
+import net.sf.briar.android.widgets.HorizontalSpace;
 import net.sf.briar.api.ContactId;
 import net.sf.briar.api.android.DatabaseUiExecutor;
 import net.sf.briar.api.crypto.CryptoComponent;
@@ -67,6 +70,7 @@ implements OnClickListener, DatabaseListener {
 
 	private GroupListAdapter adapter = null;
 	private ListView list = null;
+	private ImageButton newGroupButton = null, composeButton = null;
 
 	// Fields that are accessed from DB threads must be volatile
 	@Inject private volatile CryptoComponent crypto;
@@ -76,6 +80,7 @@ implements OnClickListener, DatabaseListener {
 	@Inject private volatile AuthorFactory authorFactory;
 	@Inject private volatile GroupFactory groupFactory;
 	@Inject private volatile MessageFactory messageFactory;
+	private volatile boolean restricted = false;
 
 	@Override
 	public void onCreate(Bundle state) {
@@ -85,6 +90,12 @@ implements OnClickListener, DatabaseListener {
 		layout.setOrientation(VERTICAL);
 		layout.setGravity(CENTER_HORIZONTAL);
 
+		Intent i = getIntent();
+		restricted = i.getBooleanExtra("net.sf.briar.RESTRICTED", false);
+		String title = i.getStringExtra("net.sf.briar.TITLE");
+		if(title == null) throw new IllegalStateException();
+		setTitle(title);
+
 		adapter = new GroupListAdapter(this);
 		list = new ListView(this);
 		// Give me all the width and all the unused height
@@ -95,11 +106,28 @@ implements OnClickListener, DatabaseListener {
 
 		layout.addView(new HorizontalBorder(this));
 
-		ImageButton newGroupButton = new ImageButton(this);
+		LinearLayout footer = new LinearLayout(this);
+		footer.setLayoutParams(CommonLayoutParams.MATCH_WRAP);
+		footer.setOrientation(HORIZONTAL);
+		footer.setGravity(CENTER);
+		footer.addView(new HorizontalSpace(this));
+
+		newGroupButton = new ImageButton(this);
 		newGroupButton.setBackgroundResource(0);
-		newGroupButton.setImageResource(R.drawable.social_new_chat);
+		if(restricted)
+			newGroupButton.setImageResource(R.drawable.social_new_blog);
+		else newGroupButton.setImageResource(R.drawable.social_new_chat);
 		newGroupButton.setOnClickListener(this);
-		layout.addView(newGroupButton);
+		footer.addView(newGroupButton);
+		footer.addView(new HorizontalSpace(this));
+
+		composeButton = new ImageButton(this);
+		composeButton.setBackgroundResource(0);
+		composeButton.setImageResource(R.drawable.content_new_email);
+		composeButton.setOnClickListener(this);
+		footer.addView(composeButton);
+		footer.addView(new HorizontalSpace(this));
+		layout.addView(footer);
 
 		setContentView(layout);
 
@@ -146,7 +174,11 @@ implements OnClickListener, DatabaseListener {
 					Group group1 = groupFactory.createGroup("Godwin's Lore");
 					db.subscribe(group1);
 					db.setVisibility(group1.getId(), Arrays.asList(contactId));
-					// Insert some text messages to the groups
+					Group group2 = groupFactory.createGroup(
+							"All Kids Love Blog", publicKey);
+					db.subscribe(group2);
+					db.setVisibility(group2.getId(), Arrays.asList(contactId));
+					// Insert some text messages to the unrestricted groups
 					for(int i = 0; i < 20; i++) {
 						String body;
 						if(i % 3 == 0) {
@@ -197,6 +229,43 @@ implements OnClickListener, DatabaseListener {
 					m = messageFactory.createAnonymousMessage(m.getId(),
 							group1, "text/plain", body.getBytes("UTF-8"));
 					db.addLocalGroupMessage(m);
+					// Insert some text messages to the restricted group
+					for(int i = 0; i < 20; i++) {
+						if(i % 3 == 0) {
+							body = "Message " + i + " is short.";
+						} else { 
+							body = "Message " + i + " is long enough to wrap"
+									+ " onto a second line on some screens.";
+						}
+						now = System.currentTimeMillis();
+						if(i % 5 == 0) {
+							m = messageFactory.createAnonymousMessage(null,
+									group2, privateKey, "text/plain",
+									body.getBytes("UTF-8"));
+						} else if(i % 5 == 2) {
+							m = messageFactory.createPseudonymousMessage(null,
+									group2, privateKey, author, privateKey,
+									"text/plain", body.getBytes("UTF-8"));
+						} else {
+							m = messageFactory.createPseudonymousMessage(null,
+									group2, privateKey, author1, privateKey,
+									"text/plain", body.getBytes("UTF-8"));
+						}
+						duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO)) {
+							LOG.info("Message creation took " +
+									duration + " ms");
+						}
+						now = System.currentTimeMillis();
+						if(Math.random() < 0.5) db.addLocalGroupMessage(m);
+						else db.receiveMessage(contactId, m);
+						db.setReadFlag(m.getId(), i % 4 == 0);
+						duration = System.currentTimeMillis() - now;
+						if(LOG.isLoggable(INFO)) {
+							LOG.info("Message storage took " +
+									duration + " ms");
+						}
+					}
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
@@ -233,8 +302,8 @@ implements OnClickListener, DatabaseListener {
 							new ArrayList<CountDownLatch>();
 					long now = System.currentTimeMillis();
 					for(Group g : db.getSubscriptions()) {
-						// Filter out restricted groups
-						if(g.getPublicKey() != null) continue;
+						// Filter out restricted/unrestricted groups
+						if(g.isRestricted() != restricted) continue;
 						try {
 							// Load the headers from the database
 							Collection<GroupMessageHeader> headers =
@@ -322,41 +391,52 @@ implements OnClickListener, DatabaseListener {
 	}
 
 	public void onClick(View view) {
-		startActivity(new Intent(this, WriteGroupMessageActivity.class));
+		if(view == newGroupButton) {
+			// FIXME: Hook this button up to an activity
+		} else if(view == composeButton) {
+			Intent i = new Intent(this, WriteGroupMessageActivity.class);
+			i.putExtra("net.sf.briar.RESTRICTED", restricted);
+			startActivity(i);
+		}
 	}
 
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
-			if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
-			loadHeaders(((GroupMessageAddedEvent) e).getGroupId());
+			Group g = ((GroupMessageAddedEvent) e).getGroup();
+			if(g.isRestricted() == restricted) {
+				if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
+				loadHeaders(g);
+			}
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
 			loadHeaders(); // FIXME: Don't reload everything
 		} else if(e instanceof SubscriptionRemovedEvent) {
 			// Reload the group, expecting NoSuchSubscriptionException
-			if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
-			loadHeaders(((SubscriptionRemovedEvent) e).getGroupId());
+			Group g = ((SubscriptionRemovedEvent) e).getGroup();
+			if(g.isRestricted() == restricted) {
+				if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
+				loadHeaders(g);
+			}
 		}
 	}
 
-	private void loadHeaders(final GroupId g) {
+	private void loadHeaders(final Group g) {
 		dbUiExecutor.execute(new Runnable() {
 			public void run() {
 				try {
 					serviceConnection.waitForStartup();
 					long now = System.currentTimeMillis();
-					Group group = db.getGroup(g);
 					Collection<GroupMessageHeader> headers =
-							db.getMessageHeaders(g);
+							db.getMessageHeaders(g.getId());
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Partial load took " + duration + " ms");
 					CountDownLatch latch = new CountDownLatch(1);
-					displayHeaders(latch, group, headers);
+					displayHeaders(latch, g, headers);
 					latch.await();
 				} catch(NoSuchSubscriptionException e) {
 					if(LOG.isLoggable(INFO)) LOG.info("Subscription removed");
-					removeGroup(g);
+					removeGroup(g.getId());
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
index 6dd8792ea63efbaa88f8f77c5eb5b27271f45d05..38b8ce59daf3e4209ab13357bd7b575a8826b71b 100644
--- a/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupMessageActivity.java
@@ -53,6 +53,7 @@ implements OnClickListener, OnItemSelectedListener {
 			new BriarServiceConnection();
 
 	@Inject private BundleEncrypter bundleEncrypter;
+	private boolean restricted = false;
 	private GroupNameSpinnerAdapter adapter = null;
 	private Spinner spinner = null;
 	private ImageButton sendButton = null;
@@ -71,6 +72,7 @@ implements OnClickListener, OnItemSelectedListener {
 		super.onCreate(null);
 
 		Intent i = getIntent();
+		restricted = i.getBooleanExtra("net.sf.briar.RESTRICTED", false);
 		byte[] id = i.getByteArrayExtra("net.sf.briar.GROUP_ID");
 		if(id != null) groupId = new GroupId(id);
 		id = i.getByteArrayExtra("net.sf.briar.PARENT_ID");
@@ -123,6 +125,7 @@ implements OnClickListener, OnItemSelectedListener {
 				serviceConnection, 0);
 	}
 
+	// FIXME: If restricted, only load groups the user can post to
 	private void loadGroupList() {
 		dbExecutor.execute(new Runnable() {
 			public void run() {
@@ -144,6 +147,7 @@ implements OnClickListener, OnItemSelectedListener {
 		runOnUiThread(new Runnable() {
 			public void run() {
 				for(Group g : groups) {
+					if(g.isRestricted() != restricted) continue;
 					if(g.getId().equals(groupId)) {
 						group = g;
 						spinner.setSelection(adapter.getCount());
diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index 3e466d1918fd02dce4b0f4a45b8b7a6ef01b778f..add29d7abd1e926fe08efc02b5f63049b6b5cfa2 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -327,5 +327,5 @@ public interface DatabaseComponent {
 	 * Unsubscribes from the given group. Any messages belonging to the group
 	 * are deleted from the database.
 	 */
-	void unsubscribe(GroupId g) throws DbException;
+	void unsubscribe(Group g) throws DbException;
 }
diff --git a/briar-api/src/net/sf/briar/api/db/event/GroupMessageAddedEvent.java b/briar-api/src/net/sf/briar/api/db/event/GroupMessageAddedEvent.java
index 0a66d81f4a21633585bee6abbbdc9af53897dfa9..40c5510301474b16b29839278221b9c584739a82 100644
--- a/briar-api/src/net/sf/briar/api/db/event/GroupMessageAddedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/GroupMessageAddedEvent.java
@@ -1,20 +1,20 @@
 package net.sf.briar.api.db.event;
 
-import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.Group;
 
 /** An event that is broadcast when a group message is added to the database. */
 public class GroupMessageAddedEvent extends DatabaseEvent {
 
-	private final GroupId groupId;
+	private final Group group;
 	private final boolean incoming;
 
-	public GroupMessageAddedEvent(GroupId groupId, boolean incoming) {
-		this.groupId = groupId;
+	public GroupMessageAddedEvent(Group group, boolean incoming) {
+		this.group = group;
 		this.incoming = incoming;
 	}
 
-	public GroupId getGroupId() {
-		return groupId;
+	public Group getGroup() {
+		return group;
 	}
 
 	public boolean isIncoming() {
diff --git a/briar-api/src/net/sf/briar/api/db/event/SubscriptionRemovedEvent.java b/briar-api/src/net/sf/briar/api/db/event/SubscriptionRemovedEvent.java
index df7f5bf9345f935bb29115ec94435cc9cdf85235..2957304efcab710e6085e4a4fdb0104bea936f77 100644
--- a/briar-api/src/net/sf/briar/api/db/event/SubscriptionRemovedEvent.java
+++ b/briar-api/src/net/sf/briar/api/db/event/SubscriptionRemovedEvent.java
@@ -1,17 +1,17 @@
 package net.sf.briar.api.db.event;
 
-import net.sf.briar.api.messaging.GroupId;
+import net.sf.briar.api.messaging.Group;
 
 /** An event that is broadcast when the user unsubscribes from a group. */
 public class SubscriptionRemovedEvent extends DatabaseEvent {
 
-	private final GroupId groupId;
+	private final Group group;
 
-	public SubscriptionRemovedEvent(GroupId groupId) {
-		this.groupId = groupId;
+	public SubscriptionRemovedEvent(Group group) {
+		this.group = group;
 	}
 
-	public GroupId getGroupId() {
-		return groupId;
+	public Group getGroup() {
+		return group;
 	}
 }
diff --git a/briar-api/src/net/sf/briar/api/messaging/Group.java b/briar-api/src/net/sf/briar/api/messaging/Group.java
index 3e1bf6466ff9894a03820b26084fb00a58f8a80b..71d260ac8439699e43f4c5175cf0b1ed0b6a1b5f 100644
--- a/briar-api/src/net/sf/briar/api/messaging/Group.java
+++ b/briar-api/src/net/sf/briar/api/messaging/Group.java
@@ -23,6 +23,11 @@ public class Group {
 		return name;
 	}
 
+	/** Returns true if the group is restricted. */
+	public boolean isRestricted() {
+		return publicKey != null;
+	}
+
 	/**
 	 * If the group is restricted, returns the public key that is used to
 	 * authorise all messages sent to the group. Otherwise returns null.
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 99dc9c162a9729ac60e398915b627825fb8f4228..f5184749ca9db506e8ab2b9fed94af00a3dbd5f5 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -287,10 +287,8 @@ DatabaseCleaner.Callback {
 		} finally {
 			contactLock.readLock().unlock();
 		}
-		if(added) {
-			GroupId g = m.getGroup().getId();
-			callListeners(new GroupMessageAddedEvent(g, false));
-		}
+		if(added)
+			callListeners(new GroupMessageAddedEvent(m.getGroup(), false));
 	}
 
 	/**
@@ -1357,7 +1355,7 @@ DatabaseCleaner.Callback {
 		if(added) {
 			Group g = m.getGroup();
 			if(g == null) callListeners(new PrivateMessageAddedEvent(c, true));
-			else callListeners(new GroupMessageAddedEvent(g.getId(), true));
+			else callListeners(new GroupMessageAddedEvent(g, true));
 		}
 	}
 
@@ -1855,7 +1853,7 @@ DatabaseCleaner.Callback {
 		return added;
 	}
 
-	public void unsubscribe(GroupId g) throws DbException {
+	public void unsubscribe(Group g) throws DbException {
 		Collection<ContactId> affected;
 		messageLock.writeLock().lock();
 		try {
@@ -1863,10 +1861,11 @@ DatabaseCleaner.Callback {
 			try {
 				T txn = db.startTransaction();
 				try {
-					if(!db.containsSubscription(txn, g))
+					GroupId id = g.getId();
+					if(!db.containsSubscription(txn, id))
 						throw new NoSuchSubscriptionException();
-					affected = db.getVisibility(txn, g);
-					db.removeSubscription(txn, g);
+					affected = db.getVisibility(txn, id);
+					db.removeSubscription(txn, id);
 					db.commitTransaction(txn);
 				} catch(DbException e) {
 					db.abortTransaction(txn);
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index 854fe74e733fb2fe66d3024f8ccf442d72c36cb1..c9834381a5c653721f57e9990a06756f18e79324 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -823,7 +823,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getId().getBytes());
 			ps.setString(2, g.getName());
-			ps.setBytes(3, g.getPublicKey());
+			if(g.isRestricted()) ps.setBytes(3, g.getPublicKey());
+			else ps.setNull(3, BINARY);
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -3029,9 +3030,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			for(Group g : subs) {
 				ps.setBytes(2, g.getId().getBytes());
 				ps.setString(3, g.getName());
-				byte[] key = g.getPublicKey();
-				if(key == null) ps.setNull(4, BINARY);
-				else ps.setBytes(4, key);
+				if(g.isRestricted()) ps.setBytes(4, g.getPublicKey());
+				else ps.setNull(4, BINARY);
 				ps.addBatch();
 			}
 			int[] affectedBatch = ps.executeBatch();
diff --git a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
index a66b3b6bf220521ce91ec19239c96863c89c1924..a6c93b06ed8c720c69f979363a4bcc670364e0a7 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
@@ -99,8 +99,7 @@ class MessageFactoryImpl implements MessageFactory {
 		// Validate the arguments
 		if((author == null) != (authorKey == null))
 			throw new IllegalArgumentException();
-		if((group == null || group.getPublicKey() == null)
-				!= (groupKey == null))
+		if((group == null || !group.isRestricted()) != (groupKey == null))
 			throw new IllegalArgumentException();
 		if(contentType.getBytes("UTF-8").length > MAX_CONTENT_TYPE_LENGTH)
 			throw new IllegalArgumentException();
@@ -182,9 +181,8 @@ class MessageFactoryImpl implements MessageFactory {
 	private void writeGroup(Writer w, Group g) throws IOException {
 		w.writeStructId(GROUP);
 		w.writeString(g.getName());
-		byte[] publicKey = g.getPublicKey();
-		if(publicKey == null) w.writeNull();
-		else w.writeBytes(publicKey);
+		if(g.isRestricted()) w.writeBytes(g.getPublicKey());
+		else w.writeNull();
 	}
 
 	private void writeAuthor(Writer w, Author a) throws IOException {
diff --git a/briar-core/src/net/sf/briar/messaging/MessageReader.java b/briar-core/src/net/sf/briar/messaging/MessageReader.java
index de0bf74ac5b67b4087b27b2fdb04f165ddf1a504..73c05505160e0bb0e66f548322f25ff94bcaab74 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageReader.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageReader.java
@@ -93,7 +93,7 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 		int signedByGroup = (int) counting.getCount();
 		// Read the group's signature, if there is one
 		byte[] groupSig = null;
-		if(group == null || group.getPublicKey() == null) r.readNull();
+		if(group == null || !group.isRestricted()) r.readNull();
 		else groupSig = r.readBytes(MAX_SIGNATURE_LENGTH);
 		// That's all, folks
 		r.removeConsumer(counting);
diff --git a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
index 22abc26555f8a31628cbe869a13ef8997c93dbce..de57bbb3a1e8a8164723aae7a0c9aea747a4a84e 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
@@ -46,7 +46,7 @@ class MessageVerifierImpl implements MessageVerifier {
 		}
 		// Verify the group's signature, if there is one
 		Group group = m.getGroup();
-		if(group != null && group.getPublicKey() != null) {
+		if(group != null && group.isRestricted()) {
 			PublicKey k = keyParser.parsePublicKey(group.getPublicKey());
 			signature.initVerify(k);
 			signature.update(raw, 0, m.getLengthSignedByGroup());
diff --git a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
index 0e54cec9b92ca5e2909bb5f880441bea79d01676..067881cb5649500648b078f4ede9889693764ce9 100644
--- a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
@@ -133,9 +133,8 @@ class PacketWriterImpl implements PacketWriter {
 		for(Group g : u.getGroups()) {
 			w.writeStructId(GROUP);
 			w.writeString(g.getName());
-			byte[] publicKey = g.getPublicKey();
-			if(publicKey == null) w.writeNull();
-			else w.writeBytes(publicKey);
+			if(g.isRestricted()) w.writeBytes(g.getPublicKey());
+			else w.writeNull();
 		}
 		w.writeListEnd();
 		w.writeInt64(u.getVersion());
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index e59726b0f8a2e05808f7f4a7c7177b6c9197e869..61bc729a9d6a7d6707b970c0f932ae25c305c84c 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -204,8 +204,8 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		db.subscribe(group); // Second time - not called
 		assertEquals(Collections.emptyList(), db.getMessageHeaders(groupId));
 		assertEquals(Arrays.asList(groupId), db.getSubscriptions());
-		db.unsubscribe(groupId); // Listeners called
-		db.removeContact(contactId); // Listeners called
+		db.unsubscribe(group);
+		db.removeContact(contactId);
 		db.removeListener(listener);
 		db.close();
 
@@ -707,7 +707,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		} catch(NoSuchSubscriptionException expected) {}
 
 		try {
-			db.unsubscribe(groupId);
+			db.unsubscribe(group);
 			fail();
 		} catch(NoSuchSubscriptionException expected) {}