diff --git a/briar-android/res/drawable-hdpi/social_blog.png b/briar-android/res/drawable-hdpi/social_blog.png
deleted file mode 100644
index dfafb709b7322d9a0ff679c975e1835ec1499966..0000000000000000000000000000000000000000
Binary files a/briar-android/res/drawable-hdpi/social_blog.png and /dev/null differ
diff --git a/briar-android/res/drawable-hdpi/social_new_blog.png b/briar-android/res/drawable-hdpi/social_new_blog.png
deleted file mode 100644
index ad2c5f31a36616193928b7cb7239a15f6b9b52ed..0000000000000000000000000000000000000000
Binary files a/briar-android/res/drawable-hdpi/social_new_blog.png and /dev/null differ
diff --git a/briar-android/res/drawable-mdpi/social_blog.png b/briar-android/res/drawable-mdpi/social_blog.png
deleted file mode 100644
index 22a330fec002129d3251a169101e84b3ef9b7be7..0000000000000000000000000000000000000000
Binary files a/briar-android/res/drawable-mdpi/social_blog.png and /dev/null differ
diff --git a/briar-android/res/drawable-mdpi/social_new_blog.png b/briar-android/res/drawable-mdpi/social_new_blog.png
deleted file mode 100644
index 039e5f8537e4480d6ce1e247862cd8a18908e452..0000000000000000000000000000000000000000
Binary files a/briar-android/res/drawable-mdpi/social_new_blog.png and /dev/null differ
diff --git a/briar-android/res/drawable-xhdpi/social_blog.png b/briar-android/res/drawable-xhdpi/social_blog.png
deleted file mode 100644
index 951a5d3f67822aadfe1d1401e4d348e929206d07..0000000000000000000000000000000000000000
Binary files a/briar-android/res/drawable-xhdpi/social_blog.png and /dev/null differ
diff --git a/briar-android/res/drawable-xhdpi/social_new_blog.png b/briar-android/res/drawable-xhdpi/social_new_blog.png
deleted file mode 100644
index ee25de145dbcde7cb2f4ce316103adf866ee287e..0000000000000000000000000000000000000000
Binary files a/briar-android/res/drawable-xhdpi/social_new_blog.png and /dev/null differ
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 9f6741d6b28e804a4c0e9b336cb96b4972a493c6..613b66ba82f80afec23754d76485b49b79905b5c 100644
--- a/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/GroupListActivity.java
@@ -140,7 +140,6 @@ OnItemClickListener {
 					long now = System.currentTimeMillis();
 					for(GroupStatus s : db.getAvailableGroups()) {
 						Group g = s.getGroup();
-						if(g.isRestricted()) continue;
 						if(s.isSubscribed()) {
 							try {
 								Collection<GroupMessageHeader> headers =
@@ -242,11 +241,8 @@ OnItemClickListener {
 
 	public void eventOccurred(DatabaseEvent e) {
 		if(e instanceof GroupMessageAddedEvent) {
-			Group g = ((GroupMessageAddedEvent) e).getGroup();
-			if(!g.isRestricted()) {
-				if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
-				loadHeaders(g);
-			}
+			if(LOG.isLoggable(INFO)) LOG.info("Message added, reloading");
+			loadHeaders(((GroupMessageAddedEvent) e).getGroup());
 		} else if(e instanceof MessageExpiredEvent) {
 			if(LOG.isLoggable(INFO)) LOG.info("Message expired, reloading");
 			loadHeaders();
@@ -255,18 +251,12 @@ OnItemClickListener {
 				LOG.info("Remote subscriptions changed, reloading");
 			loadAvailable();
 		} else if(e instanceof SubscriptionAddedEvent) {
-			Group g = ((SubscriptionAddedEvent) e).getGroup();
-			if(!g.isRestricted()) {
-				if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
-				loadHeaders();
-			}
+			if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
+			loadHeaders();
 		} else if(e instanceof SubscriptionRemovedEvent) {
-			Group g = ((SubscriptionRemovedEvent) e).getGroup();
-			if(!g.isRestricted()) {
-				// Reload the group, expecting NoSuchSubscriptionException
-				if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
-				loadHeaders(g);
-			}
+			// Reload the group, expecting NoSuchSubscriptionException
+			if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
+			loadHeaders(((SubscriptionRemovedEvent) e).getGroup());
 		}
 	}
 
@@ -317,10 +307,8 @@ OnItemClickListener {
 					lifecycleManager.waitForDatabase();
 					int available = 0;
 					long now = System.currentTimeMillis();
-					for(GroupStatus s : db.getAvailableGroups()) {
-						if(!s.getGroup().isRestricted() && !s.isSubscribed())
-							available++;
-					}
+					for(GroupStatus s : db.getAvailableGroups())
+						if(!s.isSubscribed()) available++;
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loading available took " + duration + " ms");
diff --git a/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java b/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
index bba527fe7166477a79e960496739640ec06aef29..d597be21b1af0fed1dd26dca5ac7eb45f083ccef 100644
--- a/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/ManageGroupsActivity.java
@@ -5,11 +5,8 @@ import static java.util.logging.Level.WARNING;
 import static net.sf.briar.android.groups.ManageGroupsItem.NONE;
 import static net.sf.briar.android.util.CommonLayoutParams.MATCH_MATCH;
 
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.Comparator;
-import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -78,13 +75,10 @@ implements DatabaseListener, OnItemClickListener {
 				try {
 					lifecycleManager.waitForDatabase();
 					long now = System.currentTimeMillis();
-					List<GroupStatus> available = new ArrayList<GroupStatus>();
-					for(GroupStatus s : db.getAvailableGroups())
-						if(!s.getGroup().isRestricted()) available.add(s);
+					Collection<GroupStatus> available = db.getAvailableGroups();
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Load took " + duration + " ms");
-					available = Collections.unmodifiableList(available);
 					displayAvailableGroups(available);
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
@@ -124,17 +118,11 @@ implements DatabaseListener, OnItemClickListener {
 				LOG.info("Remote subscriptions changed, reloading");
 			loadAvailableGroups();
 		} else if(e instanceof SubscriptionAddedEvent) {
-			Group g = ((SubscriptionAddedEvent) e).getGroup();
-			if(g.isRestricted()) {
-				if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
-				loadAvailableGroups();
-			}
+			if(LOG.isLoggable(INFO)) LOG.info("Group added, reloading");
+			loadAvailableGroups();
 		} else if(e instanceof SubscriptionRemovedEvent) {
-			Group g = ((SubscriptionRemovedEvent) e).getGroup();
-			if(g.isRestricted()) {
-				if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
-				loadAvailableGroups();
-			}
+			if(LOG.isLoggable(INFO)) LOG.info("Group removed, reloading");
+			loadAvailableGroups();
 		}
 	}
 
diff --git a/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
index 5c7b3ac37d8a7fe8baa459b02973a809ee035a9f..fa4995331b89591c8b7e70419492335615d696d8 100644
--- a/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
+++ b/briar-android/src/net/sf/briar/android/groups/WriteGroupPostActivity.java
@@ -11,10 +11,7 @@ import static net.sf.briar.android.util.CommonLayoutParams.MATCH_WRAP;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
-import java.util.ArrayList;
 import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
 import java.util.concurrent.Executor;
 import java.util.logging.Logger;
 
@@ -213,14 +210,12 @@ implements OnItemSelectedListener, OnClickListener {
 			public void run() {
 				try {
 					lifecycleManager.waitForDatabase();
-					List<Group> groups = new ArrayList<Group>();
 					long now = System.currentTimeMillis();
-					for(Group g : db.getSubscriptions())
-						if(!g.isRestricted()) groups.add(g);
+					Collection<Group> groups = db.getSubscriptions();
 					long duration = System.currentTimeMillis() - now;
 					if(LOG.isLoggable(INFO))
 						LOG.info("Loading groups took " + duration + " ms");
-					displayGroups(Collections.unmodifiableList(groups));
+					displayGroups(groups);
 				} catch(DbException e) {
 					if(LOG.isLoggable(WARNING))
 						LOG.log(WARNING, e.toString(), e);
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 c0b3e43e43dd577926237cd1f35aea5c13757e6a..d0c7a9f1b39d40179a7777e54c2344d5399aa5dd 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -17,7 +17,6 @@ import net.sf.briar.api.messaging.Ack;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.GroupStatus;
-import net.sf.briar.api.messaging.LocalGroup;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Offer;
@@ -62,12 +61,6 @@ public interface DatabaseComponent {
 	/** Stores a pseudonym that the user can use to sign messages. */
 	void addLocalAuthor(LocalAuthor a) throws DbException;
 
-	/**
-	 * Stores a restricted group to which the user can post messages. Storing
-	 * a group does not create a subscription to it.
-	 */
-	void addLocalGroup(LocalGroup g) throws DbException;
-
 	/** Stores a locally generated group message. */
 	void addLocalGroupMessage(Message m) throws DbException;
 
@@ -194,9 +187,6 @@ public interface DatabaseComponent {
 	/** Returns all pseudonyms that the user can use to sign messages. */
 	Collection<LocalAuthor> getLocalAuthors() throws DbException;
 
-	/** Returns all restricted groups to which the user can post messages. */
-	Collection<LocalGroup> getLocalGroups() throws DbException;
-
 	/** Returns the local transport properties for all transports. */
 	Map<TransportId, TransportProperties> getLocalProperties()
 			throws DbException;
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 eed2421f8483879c5cf8b8f02d5442a7bd15838c..a778b24dffdf7dec0c0c4b82f6d5dbcd4b316ce1 100644
--- a/briar-api/src/net/sf/briar/api/messaging/Group.java
+++ b/briar-api/src/net/sf/briar/api/messaging/Group.java
@@ -5,12 +5,12 @@ public class Group {
 
 	private final GroupId id;
 	private final String name;
-	private final byte[] publicKey;
+	private final byte[] salt;
 
-	public Group(GroupId id, String name, byte[] publicKey) {
+	public Group(GroupId id, String name, byte[] salt) {
 		this.id = id;
 		this.name = name;
-		this.publicKey = publicKey;
+		this.salt = salt;
 	}
 
 	/** Returns the group's unique identifier. */
@@ -23,18 +23,12 @@ 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 used to verify the
-	 * signatures on all messages sent to the group. If the group is
-	 * unrestricted, returns null.
+	 * Returns the salt used to distinguish the group from other groups with
+	 * the same name.
 	 */
-	public byte[] getPublicKey() {
-		return publicKey;
+	public byte[] getSalt() {
+		return salt;
 	}
 
 	@Override
diff --git a/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java b/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java
index 6fd7b4ab611945db9bd5829008ab97182769e4e5..1535cab6cfd7aa674df73e9d63caf24f5a019a6d 100644
--- a/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java
+++ b/briar-api/src/net/sf/briar/api/messaging/GroupFactory.java
@@ -4,13 +4,9 @@ import java.io.IOException;
 
 public interface GroupFactory {
 
-	/** Creates an unrestricted group. */
+	/** Creates a group with the given name and a random salt. */
 	Group createGroup(String name) throws IOException;
 
-	/** Creates a restricted group. */
-	Group createGroup(String name, byte[] publicKey) throws IOException;
-
-	/** Creates a restricted group to which the local user can post messages. */
-	LocalGroup createLocalGroup(String name, byte[] publicKey,
-			byte[] privateKey) throws IOException;
+	/** Creates a group with the given name and salt. */
+	Group createGroup(String name, byte[] salt) throws IOException;
 }
diff --git a/briar-api/src/net/sf/briar/api/messaging/LocalGroup.java b/briar-api/src/net/sf/briar/api/messaging/LocalGroup.java
deleted file mode 100644
index 3b4e4ae5d4de2a13f253bede9f03335035a097c5..0000000000000000000000000000000000000000
--- a/briar-api/src/net/sf/briar/api/messaging/LocalGroup.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package net.sf.briar.api.messaging;
-
-/** A restricted group to which the local user can post messages. */
-public class LocalGroup extends Group {
-
-	private final byte[] privateKey;
-
-	public LocalGroup(GroupId id, String name, byte[] publicKey,
-			byte[] privateKey) {
-		super(id, name, publicKey);
-		this.privateKey = privateKey;
-	}
-
-	/** Returns the private key used to sign all messages sent to the group. */
-	public byte[] getPrivateKey() {
-		return privateKey;
-	}
-}
diff --git a/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java b/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java
index 4704d319d0bdafd51cdd9a37092db4f74d955fd5..8ee5a07ca4aa8197e339fec733f6b42d332ec49f 100644
--- a/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java
+++ b/briar-api/src/net/sf/briar/api/messaging/MessageFactory.java
@@ -12,24 +12,13 @@ public interface MessageFactory {
 	Message createPrivateMessage(MessageId parent, String contentType,
 			byte[] body) throws IOException, GeneralSecurityException;
 
-	/** Creates an anonymous message to an unrestricted group. */
+	/** Creates an anonymous group message. */
 	Message createAnonymousMessage(MessageId parent, Group group,
 			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException;
 
-	/** Creates an anonymous message to a restricted group. */
-	Message createAnonymousMessage(MessageId parent, Group group,
-			PrivateKey groupKey, String contentType, byte[] body)
-					throws IOException, GeneralSecurityException;
-
-	/** Creates a pseudonymous message to an unrestricted group. */
+	/** Creates a pseudonymous group message. */
 	Message createPseudonymousMessage(MessageId parent, Group group,
-			Author author, PrivateKey authorKey, String contentType,
+			Author author, PrivateKey privateKey, String contentType,
 			byte[] body) throws IOException, GeneralSecurityException;
-
-	/** Creates a pseudonymous message to a restricted group. */
-	Message createPseudonymousMessage(MessageId parent, Group group,
-			PrivateKey groupKey, Author author, PrivateKey authorKey,
-			String contentType, byte[] body) throws IOException,
-			GeneralSecurityException;
 }
diff --git a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
index f6cd09977e74a48255d05e986c43be7738d58adb..80a1d7ddfe6c22647bc66fc5a4ecbf598382c30c 100644
--- a/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
+++ b/briar-api/src/net/sf/briar/api/messaging/MessagingConstants.java
@@ -17,6 +17,9 @@ public interface MessagingConstants {
 	/** The maximum length of a group's name in UTF-8 bytes. */
 	int MAX_GROUP_NAME_LENGTH = 50;
 
+	/** The length of a group's random salt in bytes. */
+	int GROUP_SALT_LENGTH = 32;
+
 	/**
 	 * The maximum length of a message body in bytes. To allow for future
 	 * changes in the protocol, this is smaller than the maximum packet length
@@ -31,7 +34,7 @@ public interface MessagingConstants {
 	int MAX_SUBJECT_LENGTH = 100;
 
 	/** The length of a message's random salt in bytes. */
-	int SALT_LENGTH = 8;
+	int MESSAGE_SALT_LENGTH = 32;
 
 	/**
 	 * The timestamp of the oldest message in the database is rounded using
diff --git a/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java b/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java
index bdb9ecf04876d1db503d6f7c109e5c95275244a8..bbea6bdd4db6c211b74ad2538c87bea68fd1567c 100644
--- a/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java
+++ b/briar-api/src/net/sf/briar/api/messaging/UnverifiedMessage.java
@@ -10,13 +10,12 @@ public class UnverifiedMessage {
 	private final Author author;
 	private final String contentType, subject;
 	private final long timestamp;
-	private final byte[] raw, authorSig, groupSig;
-	private final int bodyStart, bodyLength, signedByAuthor, signedByGroup;
+	private final byte[] raw, signature;
+	private final int bodyStart, bodyLength, signedLength;
 
 	public UnverifiedMessage(MessageId parent, Group group, Author author,
 			String contentType, String subject, long timestamp, byte[] raw,
-			byte[] authorSig, byte[] groupSig, int bodyStart, int bodyLength,
-			int signedByAuthor, int signedByGroup) {
+			byte[] signature, int bodyStart, int bodyLength, int signedLength) {
 		this.parent = parent;
 		this.group = group;
 		this.author = author;
@@ -24,12 +23,10 @@ public class UnverifiedMessage {
 		this.subject = subject;
 		this.timestamp = timestamp;
 		this.raw = raw;
-		this.authorSig = authorSig;
-		this.groupSig = groupSig;
+		this.signature = signature;
 		this.bodyStart = bodyStart;
 		this.bodyLength = bodyLength;
-		this.signedByAuthor = signedByAuthor;
-		this.signedByGroup = signedByGroup;
+		this.signedLength = signedLength;
 	}
 
 	/**
@@ -83,16 +80,8 @@ public class UnverifiedMessage {
 	/**
 	 * Returns the author's signature, or null if this is an anonymous message.
 	 */
-	public byte[] getAuthorSignature() {
-		return authorSig;
-	}
-
-	/**
-	 * Returns the group's signature, or null if this is a private message or
-	 * a message belonging to an unrestricted group.
-	 */
-	public byte[] getGroupSignature() {
-		return groupSig;
+	public byte[] getSignature() {
+		return signature;
 	}
 
 	/** Returns the offset of the message body within the serialised message. */
@@ -109,15 +98,7 @@ public class UnverifiedMessage {
 	 * Returns the length in bytes of the data covered by the author's
 	 * signature.
 	 */
-	public int getLengthSignedByAuthor() {
-		return signedByAuthor;
-	}
-
-	/**
-	 * Returns the length in bytes of the data covered by the group's
-	 * signature.
-	 */
-	public int getLengthSignedByGroup() {
-		return signedByGroup;
+	public int getSignedLength() {
+		return signedLength;
 	}
 }
\ No newline at end of file
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index a54751b800a8b1c67291ed999736598e5db1f457..f08ed03358d282db9db115d0a7d0ae83da1fe873 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -18,7 +18,6 @@ import net.sf.briar.api.db.PrivateMessageHeader;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.GroupStatus;
-import net.sf.briar.api.messaging.LocalGroup;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Rating;
@@ -112,14 +111,6 @@ interface Database<T> {
 	 */
 	void addLocalAuthor(T txn, LocalAuthor a) throws DbException;
 
-	/**
-	 * Stores a restricted group to which the user can post messages. Storing
-	 * a group does not create a subscription to it.
-	 * <p>
-	 * Locking: identity write.
-	 */
-	void addLocalGroup(T txn, LocalGroup g) throws DbException;
-
 	/**
 	 * Records a received message as needing to be acknowledged.
 	 * <p>
@@ -192,14 +183,6 @@ interface Database<T> {
 	 */
 	boolean containsContact(T txn, ContactId c) throws DbException;
 
-	/**
-	 * Returns true if the database contains the given restricted group to
-	 * which the user can post messages.
-	 * <p>
-	 * Locking: identity read.
-	 */
-	boolean containsLocalGroup(T txn, GroupId g) throws DbException;
-
 	/**
 	 * Returns true if the database contains the given message.
 	 * <p>
@@ -301,6 +284,14 @@ interface Database<T> {
 	 */
 	MessageId getGroupMessageParent(T txn, MessageId m) throws DbException;
 
+	/**
+	 * Returns the IDs of all group messages posted by the given author.
+	 * <p>
+	 * Locking: message read.
+	 */
+	Collection<MessageId> getGroupMessages(T txn, AuthorId a)
+			throws DbException;
+
 	/**
 	 * Returns the time at which a connection to each contact was last opened
 	 * or closed.
@@ -323,13 +314,6 @@ interface Database<T> {
 	 */
 	Collection<LocalAuthor> getLocalAuthors(T txn) throws DbException;
 
-	/**
-	 * Returns all restricted groups to which the user can post messages.
-	 * <p>
-	 * Locking: identity read.
-	 */
-	Collection<LocalGroup> getLocalGroups(T txn) throws DbException;
-
 	/**
 	 * Returns the local transport properties for all transports.
 	 * <p>
@@ -556,15 +540,6 @@ interface Database<T> {
 	 */
 	Map<GroupId, Integer> getUnreadMessageCounts(T txn) throws DbException;
 
-	/**
-	 * Returns the IDs of all messages posted by the given author to
-	 * unrestricted groups.
-	 * <p>
-	 * Locking: message read.
-	 */
-	Collection<MessageId> getUnrestrictedGroupMessages(T txn, AuthorId a)
-			throws DbException;
-
 	/**
 	 * Returns the contacts to which the given group is visible.
 	 * <p>
@@ -631,13 +606,6 @@ interface Database<T> {
 	 */
 	void removeContact(T txn, ContactId c) throws DbException;
 
-	/**
-	 * Removes the given restricted group to which the user can post messages.
-	 * <p>
-	 * Locking: identity write.
-	 */
-	void removeLocalGroup(T txn, GroupId g) throws DbException;
-
 	/**
 	 * Removes a message (and all associated state) from the database.
 	 * <p>
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index b1048be58b8ccc8a05d9136406ab510b9af88911..edc300ab6daf8f6a8ce3b27f1fd8d43a028720f5 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -65,7 +65,6 @@ import net.sf.briar.api.messaging.Ack;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.GroupStatus;
-import net.sf.briar.api.messaging.LocalGroup;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Offer;
@@ -285,22 +284,6 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public void addLocalGroup(LocalGroup g) throws DbException {
-		identityLock.writeLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				db.addLocalGroup(txn, g);
-				db.commitTransaction(txn);
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			identityLock.writeLock().unlock();
-		}
-	}
-
 	public void addLocalGroupMessage(Message m) throws DbException {
 		boolean added = false;
 		contactLock.readLock().lock();
@@ -362,13 +345,9 @@ DatabaseCleaner.Callback {
 				if(!c.equals(sender)) db.addStatus(txn, c, id, false);
 			}
 			// Calculate and store the message's sendability
-			if(m.getGroup().isRestricted()) {
-				db.setSendability(txn, id, 1);
-			} else {
-				int sendability = calculateSendability(txn, m);
-				db.setSendability(txn, id, sendability);
-				if(sendability > 0) updateAncestorSendability(txn, id, true);
-			}
+			int sendability = calculateSendability(txn, m);
+			db.setSendability(txn, id, sendability);
+			if(sendability > 0) updateAncestorSendability(txn, id, true);
 			// Count the bytes stored
 			synchronized(spaceLock) {
 				bytesStoredSinceLastCheck += m.getSerialised().length;
@@ -1066,23 +1045,6 @@ DatabaseCleaner.Callback {
 		}
 	}
 
-	public Collection<LocalGroup> getLocalGroups() throws DbException {
-		identityLock.readLock().lock();
-		try {
-			T txn = db.startTransaction();
-			try {
-				Collection<LocalGroup> groups = db.getLocalGroups(txn);
-				db.commitTransaction(txn);
-				return groups;
-			} catch(DbException e) {
-				db.abortTransaction(txn);
-				throw e;
-			}
-		} finally {
-			identityLock.readLock().unlock();
-		}
-	}
-
 	public Map<TransportId, TransportProperties> getLocalProperties()
 			throws DbException {
 		transportLock.readLock().lock();
@@ -1964,8 +1926,8 @@ DatabaseCleaner.Callback {
 	}
 
 	/**
-	 * Updates the sendability of all messages posted by the given author to
-	 * unrestricted groups, and the ancestors of those messages if necessary.
+	 * Updates the sendability of all group messages posted by the given
+	 * author, and the ancestors of those messages if necessary.
 	 * <p>
 	 * Locking: message write.
 	 * @param increment true if the user's rating for the author has changed
@@ -1973,7 +1935,7 @@ DatabaseCleaner.Callback {
 	 */
 	private void updateAuthorSendability(T txn, AuthorId a, boolean increment)
 			throws DbException {
-		for(MessageId id : db.getUnrestrictedGroupMessages(txn, a)) {
+		for(MessageId id : db.getGroupMessages(txn, a)) {
 			int sendability = db.getSendability(txn, id);
 			if(increment) {
 				db.setSendability(txn, id, sendability + 1);
@@ -2125,8 +2087,6 @@ DatabaseCleaner.Callback {
 							throw new NoSuchSubscriptionException();
 						affected = db.getVisibility(txn, id);
 						db.removeSubscription(txn, id);
-						if(db.containsLocalGroup(txn, id))
-							db.removeLocalGroup(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 bf70e237fef5de3179ffab23a3d248123977429c..c42b223ef878d00afa04a35621c419f2075eb7b6 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -43,7 +43,6 @@ import net.sf.briar.api.db.PrivateMessageHeader;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupId;
 import net.sf.briar.api.messaging.GroupStatus;
-import net.sf.briar.api.messaging.LocalGroup;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.Rating;
@@ -71,15 +70,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " privateKey BINARY NOT NULL,"
 					+ " PRIMARY KEY (authorId))";
 
-	// Locking: identity
-	private static final String CREATE_LOCAL_GROUPS =
-			"CREATE TABLE localGroups"
-					+ " (groupId HASH NOT NULL,"
-					+ " name VARCHAR NOT NULL,"
-					+ " publicKey BINARY NOT NULL,"
-					+ " privateKey BINARY NOT NULL,"
-					+ " PRIMARY KEY (groupId))";
-
 	// Locking: contact
 	// Dependents: message, retention, subscription, transport, window
 	private static final String CREATE_CONTACTS =
@@ -104,7 +94,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			"CREATE TABLE groups"
 					+ " (groupId HASH NOT NULL,"
 					+ " name VARCHAR NOT NULL,"
-					+ " publicKey BINARY," // Null for unrestricted groups
+					+ " salt BINARY NOT NULL,"
 					+ " visibleToAll BOOLEAN NOT NULL,"
 					+ " PRIMARY KEY (groupId))";
 
@@ -126,7 +116,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 					+ " (contactId INT NOT NULL,"
 					+ " groupId HASH NOT NULL," // Not a foreign key
 					+ " name VARCHAR NOT NULL,"
-					+ " publicKey BINARY," // Null for unrestricted groups
+					+ " salt BINARY NOT NULL,"
 					+ " PRIMARY KEY (contactId, groupId),"
 					+ " FOREIGN KEY (contactId)"
 					+ " REFERENCES contacts (contactId)"
@@ -406,7 +396,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		try {
 			s = txn.createStatement();
 			s.executeUpdate(insertTypeNames(CREATE_LOCAL_AUTHORS));
-			s.executeUpdate(insertTypeNames(CREATE_LOCAL_GROUPS));
 			s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
 			s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR);
 			s.executeUpdate(insertTypeNames(CREATE_GROUPS));
@@ -750,26 +739,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void addLocalGroup(Connection txn, LocalGroup g) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			String sql = "INSERT INTO localGroups"
-					+ " (groupId, name, publicKey, privateKey)"
-					+ " VALUES (?, ?, ?, ?)";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getId().getBytes());
-			ps.setString(2, g.getName());
-			ps.setBytes(3, g.getPublicKey());
-			ps.setBytes(4, g.getPrivateKey());
-			int affected = ps.executeUpdate();
-			if(affected != 1) throw new DbStateException();
-			ps.close();
-		} catch(SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public void addMessageToAck(Connection txn, ContactId c, MessageId m)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -916,13 +885,12 @@ abstract class JdbcDatabase implements Database<Connection> {
 			ps.close();
 			if(count > MAX_SUBSCRIPTIONS) throw new DbStateException();
 			if(count == MAX_SUBSCRIPTIONS) return false;
-			sql = "INSERT INTO groups (groupId, name, publicKey, visibleToAll)"
+			sql = "INSERT INTO groups (groupId, name, salt, visibleToAll)"
 					+ " VALUES (?, ?, ?, FALSE)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getId().getBytes());
 			ps.setString(2, g.getName());
-			if(g.isRestricted()) ps.setBytes(3, g.getPublicKey());
-			else ps.setNull(3, BINARY);
+			ps.setBytes(3, g.getSalt());
 			int affected = ps.executeUpdate();
 			if(affected != 1) throw new DbStateException();
 			ps.close();
@@ -1059,27 +1027,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean containsLocalGroup(Connection txn, GroupId g)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT NULL FROM localGroups WHERE groupId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getBytes());
-			rs = ps.executeQuery();
-			boolean found = rs.next();
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			return found;
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public boolean containsMessage(Connection txn, MessageId m)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -1172,8 +1119,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		ResultSet rs = null;
 		try {
 			// Add all subscribed groups to the list
-			String sql = "SELECT groupId, name, publicKey, visibleToAll"
-					+ " FROM groups";
+			String sql = "SELECT groupId, name, salt, visibleToAll FROM groups";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			List<GroupStatus> groups = new ArrayList<GroupStatus>();
@@ -1182,24 +1128,23 @@ abstract class JdbcDatabase implements Database<Connection> {
 				GroupId id = new GroupId(rs.getBytes(1));
 				subscribed.add(id);
 				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				Group group = new Group(id, name, publicKey);
+				byte[] salt = rs.getBytes(3);
+				Group group = new Group(id, name, salt);
 				boolean visibleToAll = rs.getBoolean(4);
 				groups.add(new GroupStatus(group, true, visibleToAll));
 			}
 			rs.close();
 			ps.close();
 			// Add all contact groups to the list, unless already added
-			sql = "SELECT DISTINCT groupId, name, publicKey"
-					+ " FROM contactGroups";
+			sql = "SELECT DISTINCT groupId, name, salt FROM contactGroups";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			while(rs.next()) {
 				GroupId id = new GroupId(rs.getBytes(1));
 				if(subscribed.contains(id)) continue;
 				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				Group group = new Group(id, name, publicKey);
+				byte[] salt = rs.getBytes(3);
+				Group group = new Group(id, name, salt);
 				groups.add(new GroupStatus(group, false, false));
 			}
 			rs.close();
@@ -1340,16 +1285,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT name, publicKey FROM groups WHERE groupId = ?";
+			String sql = "SELECT name, salt FROM groups WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getBytes());
 			rs = ps.executeQuery();
 			if(!rs.next()) throw new DbStateException();
 			String name = rs.getString(1);
-			byte[] publicKey = rs.getBytes(2);
+			byte[] salt = rs.getBytes(2);
 			rs.close();
 			ps.close();
-			return new Group(g, name, publicKey);
+			return new Group(g, name, salt);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1439,6 +1384,31 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public Collection<MessageId> getGroupMessages(Connection txn,
+			AuthorId a) throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT messageId"
+					+ " FROM messages AS m"
+					+ " JOIN groups AS g"
+					+ " ON m.groupId = g.groupId"
+					+ " WHERE authorId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setBytes(1, a.getBytes());
+			rs = ps.executeQuery();
+			List<MessageId> ids = new ArrayList<MessageId>();
+			while(rs.next()) ids.add(new MessageId(rs.getBytes(1)));
+			rs.close();
+			ps.close();
+			return Collections.unmodifiableList(ids);
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public Map<ContactId, Long> getLastConnected(Connection txn)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -1512,34 +1482,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<LocalGroup> getLocalGroups(Connection txn)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT groupId, name, publicKey, privateKey"
-					+ " FROM localGroups";
-			ps = txn.prepareStatement(sql);
-			rs = ps.executeQuery();
-			List<LocalGroup> groups = new ArrayList<LocalGroup>();
-			while(rs.next()) {
-				GroupId groupId = new GroupId(rs.getBytes(1));
-				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				byte[] privateKey = rs.getBytes(4);
-				groups.add(new LocalGroup(groupId, name, publicKey,
-						privateKey));
-			}
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableList(groups);
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Map<TransportId, TransportProperties> getLocalProperties(
 			Connection txn) throws DbException {
 		PreparedStatement ps = null;
@@ -2223,7 +2165,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean getStarredFlag(Connection txn, MessageId m) throws DbException {
+	public boolean getStarredFlag(Connection txn, MessageId m)
+			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
@@ -2249,15 +2192,15 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT groupId, name, publicKey FROM groups";
+			String sql = "SELECT groupId, name, salt FROM groups";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			List<Group> subs = new ArrayList<Group>();
 			while(rs.next()) {
 				GroupId groupId = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				subs.add(new Group(groupId, name, publicKey));
+				byte[] salt = rs.getBytes(3);
+				subs.add(new Group(groupId, name, salt));
 			}
 			rs.close();
 			ps.close();
@@ -2274,7 +2217,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT groupId, name, publicKey FROM contactGroups"
+			String sql = "SELECT groupId, name, salt FROM contactGroups"
 					+ " WHERE contactId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
@@ -2283,8 +2226,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			while(rs.next()) {
 				GroupId groupId = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				subs.add(new Group(groupId, name, publicKey));
+				byte[] salt = rs.getBytes(3);
+				subs.add(new Group(groupId, name, salt));
 			}
 			rs.close();
 			ps.close();
@@ -2336,7 +2279,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT g.groupId, name, publicKey,"
+			String sql = "SELECT g.groupId, name, salt,"
 					+ " localVersion, txCount"
 					+ " FROM groups AS g"
 					+ " JOIN groupVisibilities AS vis"
@@ -2356,8 +2299,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 			while(rs.next()) {
 				GroupId groupId = new GroupId(rs.getBytes(1));
 				String name = rs.getString(2);
-				byte[] key = rs.getBytes(3);
-				subs.add(new Group(groupId, name, key));
+				byte[] salt = rs.getBytes(3);
+				subs.add(new Group(groupId, name, salt));
 				version = rs.getLong(4);
 				txCount = rs.getInt(5);
 			}
@@ -2564,32 +2507,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public Collection<MessageId> getUnrestrictedGroupMessages(Connection txn,
-			AuthorId a) throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			String sql = "SELECT messageId"
-					+ " FROM messages AS m"
-					+ " JOIN groups AS g"
-					+ " ON m.groupId = g.groupId"
-					+ " WHERE authorId = ?"
-					+ " AND publicKey IS NULL";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, a.getBytes());
-			rs = ps.executeQuery();
-			List<MessageId> ids = new ArrayList<MessageId>();
-			while(rs.next()) ids.add(new MessageId(rs.getBytes(1)));
-			rs.close();
-			ps.close();
-			return Collections.unmodifiableList(ids);
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Collection<ContactId> getVisibility(Connection txn, GroupId g)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -2808,21 +2725,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void removeLocalGroup(Connection txn, GroupId g) throws DbException {
-		PreparedStatement ps = null;
-		try {
-			String sql = "DELETE FROM localGroups WHERE groupId = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, g.getBytes());
-			int affected = ps.executeUpdate();
-			if(affected != 1) throw new DbStateException();
-			ps.close();
-		} catch(SQLException e) {
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public void removeMessage(Connection txn, MessageId m) throws DbException {
 		PreparedStatement ps = null;
 		try {
@@ -3395,15 +3297,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 			// Store the new subscriptions, if any
 			if(subs.isEmpty()) return true;
 			sql = "INSERT INTO contactGroups"
-					+ " (contactId, groupId, name, publicKey)"
+					+ " (contactId, groupId, name, salt)"
 					+ " VALUES (?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setInt(1, c.getInt());
 			for(Group g : subs) {
 				ps.setBytes(2, g.getId().getBytes());
 				ps.setString(3, g.getName());
-				if(g.isRestricted()) ps.setBytes(4, g.getPublicKey());
-				else ps.setNull(4, BINARY);
+				ps.setBytes(4, g.getSalt());
 				ps.addBatch();
 			}
 			int[] batchAffected = ps.executeBatch();
diff --git a/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java
index c32248b8d482a0e58be7a4cc38609d5ba8063583..81c0d53752abbfd7352e75efafb0496bd11c1279 100644
--- a/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/GroupFactoryImpl.java
@@ -1,5 +1,6 @@
 package net.sf.briar.messaging;
 
+import static net.sf.briar.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
 import static net.sf.briar.api.messaging.Types.GROUP;
 
 import java.io.ByteArrayOutputStream;
@@ -10,7 +11,6 @@ import net.sf.briar.api.crypto.MessageDigest;
 import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.GroupFactory;
 import net.sf.briar.api.messaging.GroupId;
-import net.sf.briar.api.messaging.LocalGroup;
 import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
 
@@ -28,29 +28,20 @@ class GroupFactoryImpl implements GroupFactory {
 	}
 
 	public Group createGroup(String name) throws IOException {
-		return createGroup(name, null);
+		byte[] salt = new byte[GROUP_SALT_LENGTH];
+		crypto.getSecureRandom().nextBytes(salt);
+		return createGroup(name, salt);
 	}
 
-	public Group createGroup(String name, byte[] publicKey) throws IOException {
-		GroupId id = getId(name, publicKey);
-		return new Group(id, name, publicKey);
-	}
-
-	public LocalGroup createLocalGroup(String name, byte[] publicKey,
-			byte[] privateKey) throws IOException {
-		GroupId id = getId(name, publicKey);
-		return new LocalGroup(id, name, publicKey, privateKey);
-	}
-
-	private GroupId getId(String name, byte[] publicKey) throws IOException {
+	public Group createGroup(String name, byte[] salt) throws IOException {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		w.writeStructId(GROUP);
 		w.writeString(name);
-		if(publicKey == null) w.writeNull();
-		else w.writeBytes(publicKey);
+		w.writeBytes(salt);
 		MessageDigest messageDigest = crypto.getMessageDigest();
 		messageDigest.update(out.toByteArray());
-		return new GroupId(messageDigest.digest());
+		GroupId id = new GroupId(messageDigest.digest());
+		return new Group(id, name, salt);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
index 7b2b07fdea3fb2a295434991e4d257464187a299..b8e9f09f1cc141c8a98845c5710f4badf82628ef 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageFactoryImpl.java
@@ -5,7 +5,7 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
-import static net.sf.briar.api.messaging.MessagingConstants.SALT_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
 import static net.sf.briar.api.messaging.Types.AUTHOR;
 import static net.sf.briar.api.messaging.Types.GROUP;
 import static net.sf.briar.api.messaging.Types.MESSAGE;
@@ -39,7 +39,7 @@ import com.google.inject.Inject;
 
 class MessageFactoryImpl implements MessageFactory {
 
-	private final Signature authorSignature, groupSignature;
+	private final Signature signature;
 	private final SecureRandom random;
 	private final MessageDigest messageDigest;
 	private final WriterFactory writerFactory;
@@ -49,8 +49,7 @@ class MessageFactoryImpl implements MessageFactory {
 	@Inject
 	MessageFactoryImpl(CryptoComponent crypto, WriterFactory writerFactory,
 			Clock clock) {
-		authorSignature = crypto.getSignature();
-		groupSignature = crypto.getSignature();
+		signature = crypto.getSignature();
 		random = crypto.getSecureRandom();
 		messageDigest = crypto.getMessageDigest();
 		this.writerFactory = writerFactory;
@@ -60,46 +59,27 @@ class MessageFactoryImpl implements MessageFactory {
 
 	public Message createPrivateMessage(MessageId parent, String contentType,
 			byte[] body) throws IOException, GeneralSecurityException {
-		return createMessage(parent, null, null, null, null, contentType, body);
+		return createMessage(parent, null, null, null, contentType, body);
 	}
 
 	public Message createAnonymousMessage(MessageId parent, Group group,
 			String contentType, byte[] body) throws IOException,
 			GeneralSecurityException {
-		return createMessage(parent, group, null, null, null, contentType,
-				body);
-	}
-
-	public Message createAnonymousMessage(MessageId parent, Group group,
-			PrivateKey groupKey, String contentType, byte[] body)
-					throws IOException, GeneralSecurityException {
-		return createMessage(parent, group, groupKey, null, null, contentType,
-				body);
+		return createMessage(parent, group, null, null, contentType, body);
 	}
 
 	public Message createPseudonymousMessage(MessageId parent, Group group,
-			Author author, PrivateKey authorKey, String contentType,
+			Author author, PrivateKey privateKey, String contentType,
 			byte[] body) throws IOException, GeneralSecurityException {
-		return createMessage(parent, group, null, author, authorKey,
-				contentType, body);
-	}
-
-	public Message createPseudonymousMessage(MessageId parent, Group group,
-			PrivateKey groupKey, Author author, PrivateKey authorKey,
-			String contentType, byte[] body) throws IOException,
-			GeneralSecurityException {
-		return createMessage(parent, group, groupKey, author, authorKey,
-				contentType, body);
+		return createMessage(parent, group, author, privateKey, contentType,
+				body);
 	}
 
-	private Message createMessage(MessageId parent, Group group,
-			PrivateKey groupKey, Author author, PrivateKey authorKey,
-			String contentType, byte[] body) throws IOException,
-			GeneralSecurityException {
+	private Message createMessage(MessageId parent, Group group, Author author,
+			PrivateKey privateKey, String contentType, byte[] body)
+					throws IOException, GeneralSecurityException {
 		// Validate the arguments
-		if((author == null) != (authorKey == null))
-			throw new IllegalArgumentException();
-		if((group == null || !group.isRestricted()) != (groupKey == null))
+		if((author == null) != (privateKey == null))
 			throw new IllegalArgumentException();
 		if(contentType.getBytes("UTF-8").length > MAX_CONTENT_TYPE_LENGTH)
 			throw new IllegalArgumentException();
@@ -113,17 +93,11 @@ class MessageFactoryImpl implements MessageFactory {
 		w.addConsumer(counting);
 		Consumer digestingConsumer = new DigestingConsumer(messageDigest);
 		w.addConsumer(digestingConsumer);
-		Consumer authorConsumer = null;
-		if(authorKey != null) {
-			authorSignature.initSign(authorKey);
-			authorConsumer = new SigningConsumer(authorSignature);
-			w.addConsumer(authorConsumer);
-		}
-		Consumer groupConsumer = null;
-		if(groupKey != null) {
-			groupSignature.initSign(groupKey);
-			groupConsumer = new SigningConsumer(groupSignature);
-			w.addConsumer(groupConsumer);
+		Consumer signingConsumer = null;
+		if(privateKey != null) {
+			signature.initSign(privateKey);
+			signingConsumer = new SigningConsumer(signature);
+			w.addConsumer(signingConsumer);
 		}
 		// Write the message
 		w.writeStructId(MESSAGE);
@@ -136,32 +110,22 @@ class MessageFactoryImpl implements MessageFactory {
 		w.writeString(contentType);
 		long timestamp = clock.currentTimeMillis();
 		w.writeInt64(timestamp);
-		byte[] salt = new byte[SALT_LENGTH];
+		byte[] salt = new byte[MESSAGE_SALT_LENGTH];
 		random.nextBytes(salt);
 		w.writeBytes(salt);
 		w.writeBytes(body);
 		int bodyStart = (int) counting.getCount() - body.length;
 		// Sign the message with the author's private key, if there is one
-		if(authorKey == null) {
-			w.writeNull();
-		} else {
-			w.removeConsumer(authorConsumer);
-			byte[] sig = authorSignature.sign();
-			if(sig.length > MAX_SIGNATURE_LENGTH)
-				throw new IllegalArgumentException();
-			w.writeBytes(sig);
-		}
-		// Sign the message with the group's private key, if there is one
-		if(groupKey == null) {
+		if(privateKey == null) {
 			w.writeNull();
 		} else {
-			w.removeConsumer(groupConsumer);
-			byte[] sig = groupSignature.sign();
+			w.removeConsumer(signingConsumer);
+			byte[] sig = signature.sign();
 			if(sig.length > MAX_SIGNATURE_LENGTH)
 				throw new IllegalArgumentException();
 			w.writeBytes(sig);
 		}
-		// Hash the message, including the signatures, to get the message ID
+		// Hash the message, including the signature, to get the message ID
 		w.removeConsumer(digestingConsumer);
 		MessageId id = new MessageId(messageDigest.digest());
 		// If the content type is text/plain, extract a subject line
@@ -181,8 +145,7 @@ class MessageFactoryImpl implements MessageFactory {
 	private void writeGroup(Writer w, Group g) throws IOException {
 		w.writeStructId(GROUP);
 		w.writeString(g.getName());
-		if(g.isRestricted()) w.writeBytes(g.getPublicKey());
-		else w.writeNull();
+		w.writeBytes(g.getSalt());
 	}
 
 	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 2a5397cd6798eb702afdc0a54fd43016e82b8c0c..db0e9598ca631e84da909d3922e168e6419c0429 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageReader.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageReader.java
@@ -5,7 +5,7 @@ import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_CONTENT_TYPE_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_PACKET_LENGTH;
 import static net.sf.briar.api.messaging.MessagingConstants.MAX_SUBJECT_LENGTH;
-import static net.sf.briar.api.messaging.MessagingConstants.SALT_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.MESSAGE_SALT_LENGTH;
 import static net.sf.briar.api.messaging.Types.MESSAGE;
 
 import java.io.IOException;
@@ -67,8 +67,8 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 		long timestamp = r.readInt64();
 		if(timestamp < 0) throw new FormatException();
 		// Read the salt
-		byte[] salt = r.readBytes(SALT_LENGTH);
-		if(salt.length < SALT_LENGTH) throw new FormatException();
+		byte[] salt = r.readBytes(MESSAGE_SALT_LENGTH);
+		if(salt.length < MESSAGE_SALT_LENGTH) throw new FormatException();
 		// Read the message body
 		byte[] body = r.readBytes(MAX_BODY_LENGTH);
 		// If the content type is text/plain, extract a subject line
@@ -84,23 +84,17 @@ class MessageReader implements StructReader<UnverifiedMessage> {
 		// Record the offset of the body within the message
 		int bodyStart = (int) counting.getCount() - body.length;
 		// Record the length of the data covered by the author's signature
-		int signedByAuthor = (int) counting.getCount();
+		int signedLength = (int) counting.getCount();
 		// Read the author's signature, if there is one
-		byte[] authorSig = null;
+		byte[] signature = null;
 		if(author == null) r.readNull();
-		else authorSig = r.readBytes(MAX_SIGNATURE_LENGTH);
-		// Record the length of the data covered by the group's signature
-		int signedByGroup = (int) counting.getCount();
-		// Read the group's signature, if there is one
-		byte[] groupSig = null;
-		if(group == null || !group.isRestricted()) r.readNull();
-		else groupSig = r.readBytes(MAX_SIGNATURE_LENGTH);
-		// That's all, folks
+		else signature = r.readBytes(MAX_SIGNATURE_LENGTH);
+		// The signature will be verified later
 		r.removeConsumer(counting);
 		r.removeConsumer(copying);
 		byte[] raw = copying.getCopy();
 		return new UnverifiedMessage(parent, group, author, contentType,
-				subject, timestamp, raw, authorSig, groupSig, bodyStart,
-				body.length, signedByAuthor, signedByGroup);
+				subject, timestamp, raw, signature, bodyStart, body.length,
+				signedLength);
 	}
 }
diff --git a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
index 85d75d8880a8015f58ddd171a8e45db10adb8004..a0ffe69ca9aec8c3f6c7e9ce116908e82f1fbf23 100644
--- a/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/MessageVerifierImpl.java
@@ -8,7 +8,6 @@ import net.sf.briar.api.crypto.KeyParser;
 import net.sf.briar.api.crypto.MessageDigest;
 import net.sf.briar.api.crypto.PublicKey;
 import net.sf.briar.api.crypto.Signature;
-import net.sf.briar.api.messaging.Group;
 import net.sf.briar.api.messaging.Message;
 import net.sf.briar.api.messaging.MessageId;
 import net.sf.briar.api.messaging.MessageVerifier;
@@ -31,7 +30,7 @@ class MessageVerifierImpl implements MessageVerifier {
 			throws GeneralSecurityException {
 		MessageDigest messageDigest = crypto.getMessageDigest();
 		Signature signature = crypto.getSignature();
-		// Hash the message, including the signatures, to get the message ID
+		// Hash the message, including the signature, to get the message ID
 		byte[] raw = m.getSerialised();
 		messageDigest.update(raw);
 		MessageId id = new MessageId(messageDigest.digest());
@@ -40,20 +39,11 @@ class MessageVerifierImpl implements MessageVerifier {
 		if(author != null) {
 			PublicKey k = keyParser.parsePublicKey(author.getPublicKey());
 			signature.initVerify(k);
-			signature.update(raw, 0, m.getLengthSignedByAuthor());
-			if(!signature.verify(m.getAuthorSignature()))
+			signature.update(raw, 0, m.getSignedLength());
+			if(!signature.verify(m.getSignature()))
 				throw new GeneralSecurityException();
 		}
-		// Verify the group's signature, if there is one
-		Group group = m.getGroup();
-		if(group != null && group.isRestricted()) {
-			PublicKey k = keyParser.parsePublicKey(group.getPublicKey());
-			signature.initVerify(k);
-			signature.update(raw, 0, m.getLengthSignedByGroup());
-			if(!signature.verify(m.getGroupSignature()))
-				throw new GeneralSecurityException();
-		}
-		return new MessageImpl(id, m.getParent(), group, author,
+		return new MessageImpl(id, m.getParent(), m.getGroup(), author,
 				m.getContentType(), m.getSubject(), m.getTimestamp(), raw,
 				m.getBodyStart(), m.getBodyLength());
 	}
diff --git a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
index 067881cb5649500648b078f4ede9889693764ce9..9cdc897b485c8128623eda64f2d47e4329a470ad 100644
--- a/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
+++ b/briar-core/src/net/sf/briar/messaging/PacketWriterImpl.java
@@ -133,8 +133,7 @@ class PacketWriterImpl implements PacketWriter {
 		for(Group g : u.getGroups()) {
 			w.writeStructId(GROUP);
 			w.writeString(g.getName());
-			if(g.isRestricted()) w.writeBytes(g.getPublicKey());
-			else w.writeNull();
+			w.writeBytes(g.getSalt());
 		}
 		w.writeListEnd();
 		w.writeInt64(u.getVersion());
diff --git a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
index c67b30310dcd30ee1d3c7a46765afc2ec7532322..ad98a62b1847c8d50baefd8f8b6f8a68882d1ff2 100644
--- a/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/ProtocolIntegrationTest.java
@@ -68,8 +68,8 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 	private final ContactId contactId;
 	private final byte[] secret;
 	private final Author author;
-	private final Group group, group1;
-	private final Message message, message1, message2, message3;
+	private final Group group;
+	private final Message message, message1;
 	private final String authorName = "Alice";
 	private final String contentType = "text/plain";
 	private final String messageBody = "Hello world";
@@ -93,33 +93,23 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		// Create a shared secret
 		secret = new byte[32];
 		new Random().nextBytes(secret);
-		// Create two groups: one restricted, one unrestricted
+		// Create a group
 		GroupFactory groupFactory = i.getInstance(GroupFactory.class);
-		group = groupFactory.createGroup("Unrestricted group");
-		CryptoComponent crypto = i.getInstance(CryptoComponent.class);
-		KeyPair groupKeyPair = crypto.generateSignatureKeyPair();
-		group1 = groupFactory.createGroup("Restricted group",
-				groupKeyPair.getPublic().getEncoded());
+		group = groupFactory.createGroup("Group");
 		// Create an author
 		AuthorFactory authorFactory = i.getInstance(AuthorFactory.class);
+		CryptoComponent crypto = i.getInstance(CryptoComponent.class);
 		KeyPair authorKeyPair = crypto.generateSignatureKeyPair();
 		author = authorFactory.createAuthor(authorName,
 				authorKeyPair.getPublic().getEncoded());
-		// Create two messages to each group: one anonymous, one pseudonymous
+		// Create two messages to the group: one anonymous, one pseudonymous
 		MessageFactory messageFactory = i.getInstance(MessageFactory.class);
 		message = messageFactory.createAnonymousMessage(null, group,
 				contentType, messageBody.getBytes("UTF-8"));
-		message1 = messageFactory.createAnonymousMessage(null, group1,
-				groupKeyPair.getPrivate(), contentType,
-				messageBody.getBytes("UTF-8"));
-		message2 = messageFactory.createPseudonymousMessage(null, group,
+		message1 = messageFactory.createPseudonymousMessage(null, group,
 				author, authorKeyPair.getPrivate(), contentType,
 				messageBody.getBytes("UTF-8"));
-		message3 = messageFactory.createPseudonymousMessage(null, group1,
-				groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
-				contentType, messageBody.getBytes("UTF-8"));
-		messageIds = Arrays.asList(message.getId(), message1.getId(),
-				message2.getId(), message3.getId());
+		messageIds = Arrays.asList(message.getId(), message1.getId());
 		// Create some transport properties
 		transportId = new TransportId(TestUtils.getRandomId());
 		transportProperties = new TransportProperties(Collections.singletonMap(
@@ -145,18 +135,14 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 
 		writer.writeMessage(message.getSerialised());
 		writer.writeMessage(message1.getSerialised());
-		writer.writeMessage(message2.getSerialised());
-		writer.writeMessage(message3.getSerialised());
 
 		writer.writeOffer(new Offer(messageIds));
 
-		BitSet requested = new BitSet(4);
+		BitSet requested = new BitSet(2);
 		requested.set(1);
-		requested.set(3);
-		writer.writeRequest(new Request(requested, 4));
+		writer.writeRequest(new Request(requested, 2));
 
-		SubscriptionUpdate su = new SubscriptionUpdate(
-				Arrays.asList(group, group1), 1);
+		SubscriptionUpdate su = new SubscriptionUpdate(Arrays.asList(group), 1);
 		writer.writeSubscriptionUpdate(su);
 
 		TransportUpdate tu = new TransportUpdate(transportId,
@@ -191,12 +177,7 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		assertTrue(reader.hasMessage());
 		m = reader.readMessage();
 		checkMessageEquality(message1, messageVerifier.verifyMessage(m));
-		assertTrue(reader.hasMessage());
-		m = reader.readMessage();
-		checkMessageEquality(message2, messageVerifier.verifyMessage(m));
-		assertTrue(reader.hasMessage());
-		m = reader.readMessage();
-		checkMessageEquality(message3, messageVerifier.verifyMessage(m));
+		assertFalse(reader.hasMessage());
 
 		// Read the offer
 		assertTrue(reader.hasOffer());
@@ -209,15 +190,13 @@ public class ProtocolIntegrationTest extends BriarTestCase {
 		BitSet requested = req.getBitmap();
 		assertFalse(requested.get(0));
 		assertTrue(requested.get(1));
-		assertFalse(requested.get(2));
-		assertTrue(requested.get(3));
 		// If there are any padding bits, they should all be zero
-		assertEquals(2, requested.cardinality());
+		assertEquals(1, requested.cardinality());
 
 		// Read the subscription update
 		assertTrue(reader.hasSubscriptionUpdate());
 		SubscriptionUpdate su = reader.readSubscriptionUpdate();
-		assertEquals(Arrays.asList(group, group1), su.getGroups());
+		assertEquals(Arrays.asList(group), su.getGroups());
 		assertEquals(1, su.getVersion());
 
 		// Read the transport update
diff --git a/briar-tests/src/net/sf/briar/crypto/PasswordBasedKdfTest.java b/briar-tests/src/net/sf/briar/crypto/PasswordBasedKdfTest.java
index 93b92638c45e270e1bf3a93a8c76f5bc7d340677..ff14e35dd9b2ba57e2efbbc2545502a548bc0952 100644
--- a/briar-tests/src/net/sf/briar/crypto/PasswordBasedKdfTest.java
+++ b/briar-tests/src/net/sf/briar/crypto/PasswordBasedKdfTest.java
@@ -15,7 +15,7 @@ public class PasswordBasedKdfTest extends BriarTestCase {
 	public void testEncryptionAndDecryption() {
 		CryptoComponent crypto = new CryptoComponentImpl();
 		Random random = new Random();
-		byte[] input = new byte[123];
+		byte[] input = new byte[1234];
 		random.nextBytes(input);
 		char[] password = "password".toCharArray();
 		byte[] ciphertext = crypto.encryptWithPassword(input, password);
@@ -27,7 +27,7 @@ public class PasswordBasedKdfTest extends BriarTestCase {
 	public void testInvalidCiphertextReturnsNull() {
 		CryptoComponent crypto = new CryptoComponentImpl();
 		Random random = new Random();
-		byte[] input = new byte[123];
+		byte[] input = new byte[1234];
 		random.nextBytes(input);
 		char[] password = "password".toCharArray();
 		byte[] ciphertext = crypto.encryptWithPassword(input, password);
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index 1455b5246887b4cdbdf6ef386a6d3a59bb899f37..2248ef5b2d1d64a0cad0b7c498b1a2989759ccad 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -1,5 +1,7 @@
 package net.sf.briar.db;
 
+import static net.sf.briar.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
 import static net.sf.briar.api.messaging.Rating.GOOD;
 import static net.sf.briar.api.messaging.Rating.UNRATED;
 
@@ -59,8 +61,8 @@ import org.junit.Test;
 public abstract class DatabaseComponentTest extends BriarTestCase {
 
 	protected final Object txn = new Object();
-	protected final GroupId groupId, restrictedGroupId;
-	protected final Group group, restrictedGroup;
+	protected final GroupId groupId;
+	protected final Group group;
 	protected final AuthorId authorId;
 	protected final Author author;
 	protected final AuthorId localAuthorId;
@@ -80,15 +82,12 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 
 	public DatabaseComponentTest() {
 		groupId = new GroupId(TestUtils.getRandomId());
-		restrictedGroupId = new GroupId(TestUtils.getRandomId());
-		group = new Group(groupId, "Group name", null);
-		restrictedGroup = new Group(restrictedGroupId, "Restricted group name",
-				new byte[60]);
+		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
 		authorId = new AuthorId(TestUtils.getRandomId());
-		author = new Author(authorId, "Alice", new byte[60]);
+		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
-		localAuthor = new LocalAuthor(localAuthorId, "Bob", new byte[60],
-				new byte[60]);
+		localAuthor = new LocalAuthor(localAuthorId, "Bob",
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
 		privateMessageId = new MessageId(TestUtils.getRandomId());
@@ -142,7 +141,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// setRating(authorId, GOOD)
 			oneOf(database).setRating(txn, authorId, GOOD);
 			will(returnValue(UNRATED));
-			oneOf(database).getUnrestrictedGroupMessages(txn, authorId);
+			oneOf(database).getGroupMessages(txn, authorId);
 			will(returnValue(Collections.emptyList()));
 			oneOf(listener).eventOccurred(with(any(RatingChangedEvent.class)));
 			// setRating(authorId, GOOD) again
@@ -186,8 +185,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).getVisibility(txn, groupId);
 			will(returnValue(Collections.emptyList()));
 			oneOf(database).removeSubscription(txn, groupId);
-			oneOf(database).containsLocalGroup(txn, groupId);
-			will(returnValue(false));
 			oneOf(listener).eventOccurred(with(any(
 					SubscriptionRemovedEvent.class)));
 			oneOf(listener).eventOccurred(with(any(
@@ -228,59 +225,6 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 		context.assertIsSatisfied();
 	}
 
-	@Test
-	public void testRestrictedGroupMessagesAreAlwaysSendable()
-			throws Exception {
-		final Message groupMessage = new TestMessage(messageId, null,
-				restrictedGroup, author, contentType, subject, timestamp, raw);
-		final Message groupMessage1 = new TestMessage(messageId1, null,
-				restrictedGroup, null, contentType, subject, timestamp, raw);
-		Mockery context = new Mockery();
-		@SuppressWarnings("unchecked")
-		final Database<Object> database = context.mock(Database.class);
-		final DatabaseCleaner cleaner = context.mock(DatabaseCleaner.class);
-		final ShutdownManager shutdown = context.mock(ShutdownManager.class);
-		context.checking(new Expectations() {{
-			// addLocalGroupMessage(groupMessage)
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsSubscription(txn, restrictedGroupId);
-			will(returnValue(true));
-			oneOf(database).addGroupMessage(txn, groupMessage, false);
-			will(returnValue(true));
-			oneOf(database).setReadFlag(txn, messageId, true);
-			oneOf(database).getContactIds(txn);
-			will(returnValue(Arrays.asList(contactId)));
-			oneOf(database).addStatus(txn, contactId, messageId, false);
-			oneOf(database).setSendability(txn, messageId, 1);
-			oneOf(database).commitTransaction(txn);
-			// receiveMessage(groupMessage1)
-			oneOf(database).startTransaction();
-			will(returnValue(txn));
-			oneOf(database).containsContact(txn, contactId);
-			will(returnValue(true));
-			oneOf(database).containsVisibleSubscription(txn, contactId,
-					restrictedGroupId);
-			will(returnValue(true));
-			oneOf(database).addGroupMessage(txn, groupMessage1, true);
-			will(returnValue(true));
-			oneOf(database).addStatus(txn, contactId, messageId1, true);
-			oneOf(database).getContactIds(txn);
-			will(returnValue(Arrays.asList(contactId)));
-			oneOf(database).setSendability(txn, messageId1, 1);
-			oneOf(database).addMessageToAck(txn, contactId, messageId1);
-			oneOf(database).commitTransaction(txn);
-		}});
-
-		DatabaseComponent db = createDatabaseComponent(database, cleaner,
-				shutdown);
-
-		db.addLocalGroupMessage(groupMessage);
-		db.receiveMessage(contactId, groupMessage1);
-
-		context.assertIsSatisfied();
-	}
-	
 	@Test
 	public void testNullParentStopsBackwardInclusion() throws Exception {
 		Mockery context = new Mockery();
@@ -295,7 +239,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).setRating(txn, authorId, GOOD);
 			will(returnValue(UNRATED));
 			// The sendability of the author's messages should be incremented
-			oneOf(database).getUnrestrictedGroupMessages(txn, authorId);
+			oneOf(database).getGroupMessages(txn, authorId);
 			will(returnValue(Arrays.asList(messageId)));
 			oneOf(database).getSendability(txn, messageId);
 			will(returnValue(0));
@@ -327,7 +271,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).setRating(txn, authorId, GOOD);
 			will(returnValue(UNRATED));
 			// The sendability of the author's messages should be incremented
-			oneOf(database).getUnrestrictedGroupMessages(txn, authorId);
+			oneOf(database).getGroupMessages(txn, authorId);
 			will(returnValue(Arrays.asList(messageId)));
 			oneOf(database).getSendability(txn, messageId);
 			will(returnValue(0));
@@ -364,7 +308,7 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			oneOf(database).setRating(txn, authorId, GOOD);
 			will(returnValue(UNRATED));
 			// The sendability of the author's messages should be incremented
-			oneOf(database).getUnrestrictedGroupMessages(txn, authorId);
+			oneOf(database).getGroupMessages(txn, authorId);
 			will(returnValue(Arrays.asList(messageId)));
 			oneOf(database).getSendability(txn, messageId);
 			will(returnValue(0));
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index 2922c1dc4101aee719c94d0cee9c474990a2a90f..2edf5af40860df28344445a310e8cd49f8932976 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -1,6 +1,8 @@
 package net.sf.briar.db;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static net.sf.briar.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static net.sf.briar.api.messaging.MessagingConstants.GROUP_SALT_LENGTH;
 import static net.sf.briar.api.messaging.Rating.GOOD;
 import static net.sf.briar.api.messaging.Rating.UNRATED;
 import static org.junit.Assert.assertArrayEquals;
@@ -71,12 +73,12 @@ public class H2DatabaseTest extends BriarTestCase {
 
 	public H2DatabaseTest() throws Exception {
 		groupId = new GroupId(TestUtils.getRandomId());
-		group = new Group(groupId, "Group name", null);
+		group = new Group(groupId, "Group", new byte[GROUP_SALT_LENGTH]);
 		authorId = new AuthorId(TestUtils.getRandomId());
-		author = new Author(authorId, "Alice", new byte[60]);
+		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
 		localAuthorId = new AuthorId(TestUtils.getRandomId());
-		localAuthor = new LocalAuthor(localAuthorId, "Bob", new byte[60],
-				new byte[60]);
+		localAuthor = new LocalAuthor(localAuthorId, "Bob",
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
 		contentType = "text/plain";
@@ -535,38 +537,28 @@ public class H2DatabaseTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testGetUnrestrictedGroupMessages() throws Exception {
+	public void testGetGroupMessages() throws Exception {
 		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
-		Author author1 = new Author(authorId1, "Bob", new byte[60]);
+		Author author1 = new Author(authorId1, "Bob",
+				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
 		Message message1 = new TestMessage(messageId1, null, group, author1,
 				contentType, subject, timestamp, raw);
-		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = new Group(groupId1, "Restricted group name",
-				new byte[60]);
-		MessageId messageId2 = new MessageId(TestUtils.getRandomId());
-		Message message2 = new TestMessage(messageId2, null, group1, author,
-				contentType, subject, timestamp, raw);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
-		// Subscribe to an unrestricted group and store two messages
+		// Subscribe to a group and store two messages
 		db.addSubscription(txn, group);
 		db.addGroupMessage(txn, message, false);
 		db.addGroupMessage(txn, message1, false);
 
-		// Subscribe to a restricted group and store a message
-		db.addSubscription(txn, group1);
-		db.addGroupMessage(txn, message2, false);
-
-		// Check that only the messages in the unrestricted group are retrieved
-		Collection<MessageId> ids = db.getUnrestrictedGroupMessages(txn,
-				authorId);
+		// Check that both messages are retrievable by their authors
+		Collection<MessageId> ids = db.getGroupMessages(txn, authorId);
 		Iterator<MessageId> it = ids.iterator();
 		assertTrue(it.hasNext());
 		assertEquals(messageId, it.next());
 		assertFalse(it.hasNext());
-		ids = db.getUnrestrictedGroupMessages(txn, authorId1);
+		ids = db.getGroupMessages(txn, authorId1);
 		it = ids.iterator();
 		assertTrue(it.hasNext());
 		assertEquals(messageId1, it.next());
@@ -582,7 +574,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		MessageId childId2 = new MessageId(TestUtils.getRandomId());
 		MessageId childId3 = new MessageId(TestUtils.getRandomId());
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = new Group(groupId1, "Group name", null);
+		Group group1 = new Group(groupId1, "Another group",
+				new byte[GROUP_SALT_LENGTH]);
 		Message child1 = new TestMessage(childId1, messageId, group, author,
 				contentType, subject, timestamp, raw);
 		Message child2 = new TestMessage(childId2, messageId, group, author,
@@ -1193,7 +1186,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	public void testGetGroupMessageParentWithParentInAnotherGroup()
 			throws Exception {
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = new Group(groupId1, "Group name", null);
+		Group group1 = new Group(groupId1, "Another group",
+				new byte[GROUP_SALT_LENGTH]);
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
 
@@ -1446,7 +1440,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		// Subscribe to a couple of groups
 		db.addSubscription(txn, group);
 		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
-		Group group1 = new Group(groupId1, "Group name", null);
+		Group group1 = new Group(groupId1, "Another group",
+				new byte[GROUP_SALT_LENGTH]);
 		db.addSubscription(txn, group1);
 
 		// Store two messages in the first group
@@ -1499,7 +1494,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		List<Group> groups = new ArrayList<Group>();
 		for(int i = 0; i < 100; i++) {
 			GroupId id = new GroupId(TestUtils.getRandomId());
-			groups.add(new Group(id, "Group name", null));
+			String name = "Group " + i;
+			groups.add(new Group(id, name, new byte[GROUP_SALT_LENGTH]));
 		}
 
 		Database<Connection> db = open(false);
@@ -1834,7 +1830,8 @@ public class H2DatabaseTest extends BriarTestCase {
 	public void testGetAvailableGroups() throws Exception {
 		ContactId contactId1 = new ContactId(2);
 		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
-		Author author1 = new Author(authorId1, "Carol", new byte[60]);
+		Author author1 = new Author(authorId1, "Carol",
+				new byte[MAX_PUBLIC_KEY_LENGTH]);
 
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
diff --git a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
index 0760579ade6c3e82006d945835cdf03f9dc100a7..93871605ecbb8b2d299c5b1b29ef429c0aabc188 100644
--- a/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/ConstantsTest.java
@@ -96,7 +96,7 @@ public class ConstantsTest extends BriarTestCase {
 			byte[] publicKey = keyPair.getPublic().getEncoded();
 			assertTrue(publicKey.length <= MAX_PUBLIC_KEY_LENGTH);
 			// Sign some random data and check the length of the signature
-			byte[] toBeSigned = new byte[1000];
+			byte[] toBeSigned = new byte[1234];
 			random.nextBytes(toBeSigned);
 			sig.initSign(keyPair.getPrivate());
 			sig.update(toBeSigned);
@@ -120,23 +120,19 @@ public class ConstantsTest extends BriarTestCase {
 		MessageId parent = new MessageId(TestUtils.getRandomId());
 		// Create a maximum-length group
 		String groupName = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
-		byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
-		Group group = groupFactory.createGroup(groupName, groupPublic);
+		Group group = groupFactory.createGroup(groupName);
 		// Create a maximum-length author
 		String authorName =
 				TestUtils.createRandomString(MAX_AUTHOR_NAME_LENGTH);
 		byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
 		Author author = authorFactory.createAuthor(authorName, authorPublic);
 		// Create a maximum-length message
-		PrivateKey groupPrivate =
-				crypto.generateSignatureKeyPair().getPrivate();
-		PrivateKey authorPrivate =
-				crypto.generateSignatureKeyPair().getPrivate();
+		PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
 		String contentType =
 				TestUtils.createRandomString(MAX_CONTENT_TYPE_LENGTH);
 		byte[] body = new byte[MAX_BODY_LENGTH];
 		Message message = messageFactory.createPseudonymousMessage(parent,
-				group, groupPrivate, author, authorPrivate, contentType, body);
+				group, author, privateKey, contentType, body);
 		// Check the size of the serialised message
 		int length = message.getSerialised().length;
 		assertTrue(length > UniqueId.LENGTH + MAX_GROUP_NAME_LENGTH
@@ -181,10 +177,8 @@ public class ConstantsTest extends BriarTestCase {
 		// Create the maximum number of maximum-length groups
 		Collection<Group> subs = new ArrayList<Group>();
 		for(int i = 0; i < MAX_SUBSCRIPTIONS; i++) {
-			String groupName =
-					TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
-			byte[] groupPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
-			subs.add(groupFactory.createGroup(groupName, groupPublic));
+			String name = TestUtils.createRandomString(MAX_GROUP_NAME_LENGTH);
+			subs.add(groupFactory.createGroup(name));
 		}
 		// Create a maximum-length subscription update
 		SubscriptionUpdate u = new SubscriptionUpdate(subs, Long.MAX_VALUE);
diff --git a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
index 99047990632c19f4b32365810c776ea19949db26..5a0cd6067998a1156ff48d0d1dc5a8bb0e3be4d4 100644
--- a/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
+++ b/briar-tests/src/net/sf/briar/messaging/simplex/SimplexMessagingIntegrationTest.java
@@ -1,5 +1,6 @@
 package net.sf.briar.messaging.simplex;
 
+import static net.sf.briar.api.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static net.sf.briar.api.transport.TransportConstants.TAG_LENGTH;
 
 import java.io.ByteArrayInputStream;
@@ -108,11 +109,12 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		// Add a local pseudonym for Alice
 		AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
 		LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
-				new byte[60], new byte[60]);
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
 		db.addLocalAuthor(aliceAuthor);
 		// Add Bob as a contact
 		AuthorId bobId = new AuthorId(TestUtils.getRandomId());
-		Author bobAuthor = new Author(bobId, "Bob", new byte[60]);
+		Author bobAuthor = new Author(bobId, "Bob",
+				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = db.addContact(bobAuthor, aliceId);
 		// Add the transport and the endpoint
 		db.addTransport(transportId, LATENCY);
@@ -161,12 +163,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		km.start();
 		// Add a local pseudonym for Bob
 		AuthorId bobId = new AuthorId(TestUtils.getRandomId());
-		LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob", new byte[60],
-				new byte[60]);
+		LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
+				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[100]);
 		db.addLocalAuthor(bobAuthor);
 		// Add Alice as a contact
 		AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
-		Author aliceAuthor = new Author(aliceId, "Alice", new byte[60]);
+		Author aliceAuthor = new Author(aliceId, "Alice",
+				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = db.addContact(aliceAuthor, bobId);
 		// Add the transport and the endpoint
 		db.addTransport(transportId, LATENCY);
diff --git a/briar-tests/src/net/sf/briar/plugins/file/RemovableDrivePluginTest.java b/briar-tests/src/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
index ba96c4be30ebc55ffa282caf6fe225d83b114e3c..527d1d6d510a00d5e617a6b2852d3b0fb2223749 100644
--- a/briar-tests/src/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
+++ b/briar-tests/src/net/sf/briar/plugins/file/RemovableDrivePluginTest.java
@@ -263,13 +263,13 @@ public class RemovableDrivePluginTest extends BriarTestCase {
 		assertEquals(0, files[0].length());
 		// Writing to the output stream should increase the size of the file
 		OutputStream out = writer.getOutputStream();
-		out.write(new byte[123]);
+		out.write(new byte[1234]);
 		out.flush();
 		out.close();
 		// Disposing of the writer should not delete the file
 		writer.dispose(false);
 		assertTrue(files[0].exists());
-		assertEquals(123, files[0].length());
+		assertEquals(1234, files[0].length());
 
 		context.assertIsSatisfied();
 	}