From 030b9ef05325286e816ab78ec73eccb9f710e2bb Mon Sep 17 00:00:00 2001
From: akwizgran <michael@briarproject.org>
Date: Wed, 29 Nov 2017 15:48:54 +0000
Subject: [PATCH] Use a versioned format for encoding authors.

---
 .../bramble/api/client/ClientHelper.java      |   2 +
 .../bramble/api/data/ObjectReader.java        |  11 -
 .../bramble/api/identity/Author.java          |  34 +-
 .../bramble/api/identity/AuthorFactory.java   |  19 +
 .../bramble/api/identity/LocalAuthor.java     |   6 +-
 .../briarproject/bramble/test/TestUtils.java  |   6 +-
 .../bramble/client/ClientHelperImpl.java      |  24 +-
 .../bramble/client/ClientModule.java          |  23 +-
 .../contact/ContactExchangeTaskImpl.java      |  11 +-
 .../briarproject/bramble/db/JdbcDatabase.java | 114 ++--
 .../bramble/identity/AuthorFactoryImpl.java   |  54 +-
 .../bramble/identity/AuthorReader.java        |  36 --
 .../bramble/identity/IdentityModule.java      |  21 +-
 .../bramble/client/ClientHelperImplTest.java  | 177 +++++-
 .../contact/ContactManagerImplTest.java       |  11 +-
 .../bramble/db/DatabaseComponentImplTest.java |  86 +--
 .../bramble/db/JdbcDatabaseTest.java          | 129 ++--
 .../identity/IdentityManagerImplTest.java     |  25 +-
 .../TransportPropertyManagerImplTest.java     |  23 +-
 .../bramble/test/ValidatorTestCase.java       |  17 +-
 .../bramble/transport/KeyManagerImplTest.java |  18 +-
 .../android/forum/ForumActivityTest.java      |  45 +-
 .../briar/api/blog/BlogConstants.java         |   1 +
 .../briar/api/feed/FeedConstants.java         |   1 +
 .../briar/api/forum/ForumConstants.java       |  17 +-
 .../briar/blog/BlogFactoryImpl.java           |  31 +-
 .../briar/blog/BlogManagerImpl.java           |  14 +-
 .../briar/blog/BlogPostValidator.java         |  14 +-
 .../briar/feed/FeedFactoryImpl.java           |  28 +-
 .../briar/forum/ForumFactoryImpl.java         |   1 -
 .../briar/forum/ForumManagerImpl.java         |  13 +-
 .../briarproject/briar/forum/ForumModule.java |  20 +-
 .../briar/forum/ForumPostFactoryImpl.java     |   7 +-
 .../briar/forum/ForumPostValidator.java       |  47 +-
 .../briar/introduction/IntroduceeManager.java |   2 +
 .../introduction/IntroductionValidator.java   |   4 +-
 .../briar/privategroup/GroupConstants.java    |   3 +-
 .../privategroup/GroupMessageFactoryImpl.java |  78 ++-
 .../privategroup/GroupMessageValidator.java   | 154 ++---
 .../privategroup/PrivateGroupFactoryImpl.java |  40 +-
 .../privategroup/PrivateGroupManagerImpl.java |  23 +-
 .../privategroup/PrivateGroupModule.java      |   6 +-
 .../invitation/AbstractProtocolEngine.java    |   9 +-
 .../invitation/CreatorProtocolEngine.java     |   4 +-
 .../invitation/GroupInvitationModule.java     |   8 +-
 .../invitation/GroupInvitationValidator.java  |  32 +-
 .../invitation/InviteeProtocolEngine.java     |   4 +-
 .../invitation/MessageEncoderImpl.java        |   8 +-
 .../invitation/MessageParserImpl.java         |  25 +-
 .../briar/sharing/BlogMessageParserImpl.java  |  21 +-
 .../briar/sharing/BlogSharingValidator.java   |  33 +-
 .../briar/sharing/ForumMessageParserImpl.java |   4 +-
 .../briar/sharing/ForumSharingValidator.java  |   1 +
 .../briar/sharing/ProtocolEngineImpl.java     |  27 +-
 .../briar/sharing/SharingModule.java          |   5 +-
 .../briar/test/TestDataCreatorImpl.java       |   4 +-
 .../briar/blog/BlogManagerImplTest.java       |  19 +-
 .../briar/blog/BlogPostValidatorTest.java     |  53 +-
 .../briar/feed/FeedManagerImplTest.java       |   7 +-
 .../briar/forum/ForumPostValidatorTest.java   | 259 ++------
 .../introduction/IntroduceeManagerTest.java   |  58 +-
 .../introduction/IntroducerManagerTest.java   |  27 +-
 .../IntroductionIntegrationTest.java          |   9 +-
 .../IntroductionManagerImplTest.java          |  31 +-
 .../IntroductionValidatorTest.java            | 125 ++--
 .../messaging/MessageSizeIntegrationTest.java |  27 +-
 .../SimplexMessagingIntegrationTest.java      |  25 +-
 .../GroupMessageValidatorTest.java            | 563 ++++++++++--------
 .../AbstractProtocolEngineTest.java           |  32 +-
 .../GroupInvitationValidatorTest.java         | 204 +++----
 .../invitation/InviteeProtocolEngineTest.java |  23 +-
 .../sharing/BlogSharingManagerImplTest.java   |  30 +-
 .../sharing/BlogSharingValidatorTest.java     | 158 ++---
 .../sharing/ForumSharingValidatorTest.java    |  65 +-
 .../briar/sharing/SharingValidatorTest.java   |  71 ++-
 .../briar/test/BriarIntegrationTest.java      |   4 +-
 76 files changed, 1618 insertions(+), 1753 deletions(-)
 delete mode 100644 bramble-api/src/main/java/org/briarproject/bramble/api/data/ObjectReader.java
 delete mode 100644 bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorReader.java

diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java b/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java
index 74aea4ac4c..814136f4f0 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/client/ClientHelper.java
@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
@@ -99,4 +100,5 @@ public interface ClientHelper {
 	void verifySignature(String label, byte[] sig, byte[] publicKey,
 			BdfList signed) throws FormatException, GeneralSecurityException;
 
+	Author parseAndValidateAuthor(BdfList author) throws FormatException;
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/data/ObjectReader.java b/bramble-api/src/main/java/org/briarproject/bramble/api/data/ObjectReader.java
deleted file mode 100644
index acbf1434da..0000000000
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/data/ObjectReader.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.briarproject.bramble.api.data;
-
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.IOException;
-
-@NotNullByDefault
-public interface ObjectReader<T> {
-
-	T readObject(BdfReader r) throws IOException;
-}
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java
index b7c208ff29..830a32643f 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/Author.java
@@ -1,11 +1,13 @@
 package org.briarproject.bramble.api.identity;
 
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.UnsupportedEncodingException;
+import org.briarproject.bramble.util.StringUtils;
 
 import javax.annotation.concurrent.Immutable;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+
 /**
  * A pseudonym for a user.
  */
@@ -17,20 +19,25 @@ public class Author {
 		NONE, ANONYMOUS, UNKNOWN, UNVERIFIED, VERIFIED, OURSELVES
 	}
 
+	/**
+	 * The current version of the author structure.
+	 */
+	public static final int FORMAT_VERSION = 0;
+
 	private final AuthorId id;
+	private final int formatVersion;
 	private final String name;
 	private final byte[] publicKey;
 
-	public Author(AuthorId id, String name, byte[] publicKey) {
-		int length;
-		try {
-			length = name.getBytes("UTF-8").length;
-		} catch (UnsupportedEncodingException e) {
-			throw new RuntimeException(e);
-		}
-		if (length == 0 || length > AuthorConstants.MAX_AUTHOR_NAME_LENGTH)
+	public Author(AuthorId id, int formatVersion, String name,
+			byte[] publicKey) {
+		int nameLength = StringUtils.toUtf8(name).length;
+		if (nameLength == 0 || nameLength > MAX_AUTHOR_NAME_LENGTH)
+			throw new IllegalArgumentException();
+		if (publicKey.length == 0 || publicKey.length > MAX_PUBLIC_KEY_LENGTH)
 			throw new IllegalArgumentException();
 		this.id = id;
+		this.formatVersion = formatVersion;
 		this.name = name;
 		this.publicKey = publicKey;
 	}
@@ -42,6 +49,13 @@ public class Author {
 		return id;
 	}
 
+	/**
+	 * Returns the version of the author structure used to create the author.
+	 */
+	public int getFormatVersion() {
+		return formatVersion;
+	}
+
 	/**
 	 * Returns the author's name.
 	 */
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorFactory.java
index aaf151dc17..5566a2c956 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/AuthorFactory.java
@@ -5,8 +5,27 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 @NotNullByDefault
 public interface AuthorFactory {
 
+	/**
+	 * Creates an author with the current format version and the given name and
+	 * public key.
+	 */
 	Author createAuthor(String name, byte[] publicKey);
 
+	/**
+	 * Creates an author with the given format version, name and public key.
+	 */
+	Author createAuthor(int formatVersion, String name, byte[] publicKey);
+
+	/**
+	 * Creates a local author with the current format version and the given
+	 * name and keys.
+	 */
 	LocalAuthor createLocalAuthor(String name, byte[] publicKey,
 			byte[] privateKey);
+
+	/**
+	 * Creates a local author with the given format version, name and keys.
+	 */
+	LocalAuthor createLocalAuthor(int formatVersion, String name,
+			byte[] publicKey, byte[] privateKey);
 }
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/LocalAuthor.java b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/LocalAuthor.java
index ae217b19fd..0b470a6c10 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/identity/LocalAuthor.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/identity/LocalAuthor.java
@@ -14,9 +14,9 @@ public class LocalAuthor extends Author {
 	private final byte[] privateKey;
 	private final long created;
 
-	public LocalAuthor(AuthorId id, String name, byte[] publicKey,
-			byte[] privateKey, long created) {
-		super(id, name, publicKey);
+	public LocalAuthor(AuthorId id, int formatVersion, String name,
+			byte[] publicKey, byte[] privateKey, long created) {
+		super(id, formatVersion, name, publicKey);
 		this.privateKey = privateKey;
 		this.created = created;
 	}
diff --git a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java
index 749df4444b..a0fcb5e4eb 100644
--- a/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java
+++ b/bramble-api/src/test/java/org/briarproject/bramble/test/TestUtils.java
@@ -20,6 +20,7 @@ import java.util.List;
 import java.util.Random;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
@@ -67,7 +68,8 @@ public class TestUtils {
 		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
 		byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
 		long created = System.currentTimeMillis();
-		return new LocalAuthor(id, name, publicKey, privateKey, created);
+		return new LocalAuthor(id, FORMAT_VERSION, name, publicKey, privateKey,
+				created);
 	}
 
 	public static Author getAuthor() {
@@ -78,7 +80,7 @@ public class TestUtils {
 		AuthorId id = new AuthorId(getRandomId());
 		String name = getRandomString(nameLength);
 		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		return new Author(id, name, publicKey);
+		return new Author(id, FORMAT_VERSION, name, publicKey);
 	}
 
 	public static Group getGroup(ClientId clientId) {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java
index 2de54e0ce4..2af115819e 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientHelperImpl.java
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
 import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
@@ -32,7 +34,12 @@ import java.util.Map.Entry;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
+import static org.briarproject.bramble.util.ValidationUtils.checkLength;
+import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 
 @Immutable
 @NotNullByDefault
@@ -51,12 +58,14 @@ class ClientHelperImpl implements ClientHelper {
 	private final MetadataParser metadataParser;
 	private final MetadataEncoder metadataEncoder;
 	private final CryptoComponent crypto;
+	private final AuthorFactory authorFactory;
 
 	@Inject
 	ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
 			BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
-			MetadataEncoder metadataEncoder, CryptoComponent crypto) {
+			MetadataEncoder metadataEncoder, CryptoComponent crypto,
+			AuthorFactory authorFactory) {
 		this.db = db;
 		this.messageFactory = messageFactory;
 		this.bdfReaderFactory = bdfReaderFactory;
@@ -64,6 +73,7 @@ class ClientHelperImpl implements ClientHelper {
 		this.metadataParser = metadataParser;
 		this.metadataEncoder = metadataEncoder;
 		this.crypto = crypto;
+		this.authorFactory = authorFactory;
 	}
 
 	@Override
@@ -355,4 +365,16 @@ class ClientHelperImpl implements ClientHelper {
 		}
 	}
 
+	@Override
+	public Author parseAndValidateAuthor(BdfList author)
+			throws FormatException {
+		checkSize(author, 3);
+		int formatVersion = author.getLong(0).intValue();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
+		String name = author.getString(1);
+		checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
+		byte[] publicKey = author.getRaw(2);
+		checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
+		return authorFactory.createAuthor(formatVersion, name, publicKey);
+	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientModule.java b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientModule.java
index c5ec20f835..0d71c71e8d 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/client/ClientModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/client/ClientModule.java
@@ -2,14 +2,6 @@ package org.briarproject.bramble.client;
 
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.client.ContactGroupFactory;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.data.BdfReaderFactory;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.data.MetadataEncoder;
-import org.briarproject.bramble.api.data.MetadataParser;
-import org.briarproject.bramble.api.db.DatabaseComponent;
-import org.briarproject.bramble.api.sync.GroupFactory;
-import org.briarproject.bramble.api.sync.MessageFactory;
 
 import dagger.Module;
 import dagger.Provides;
@@ -18,19 +10,14 @@ import dagger.Provides;
 public class ClientModule {
 
 	@Provides
-	ClientHelper provideClientHelper(DatabaseComponent db,
-			MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
-			BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
-			MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
-		return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
-				bdfWriterFactory, metadataParser, metadataEncoder,
-				cryptoComponent);
+	ClientHelper provideClientHelper(ClientHelperImpl clientHelper) {
+		return clientHelper;
 	}
 
 	@Provides
-	ContactGroupFactory provideContactGroupFactory(GroupFactory groupFactory,
-			ClientHelper clientHelper) {
-		return new ContactGroupFactoryImpl(groupFactory, clientHelper);
+	ContactGroupFactory provideContactGroupFactory(
+			ContactGroupFactoryImpl contactGroupFactory) {
+		return contactGroupFactory;
 	}
 
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java
index 773fa1aab7..f495b56480 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactExchangeTaskImpl.java
@@ -43,6 +43,7 @@ import javax.inject.Inject;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
@@ -227,6 +228,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
 
 		// Write the name, public key and signature
 		w.writeListStart();
+		w.writeLong(localAuthor.getFormatVersion());
 		w.writeString(localAuthor.getName());
 		w.writeRaw(localAuthor.getPublicKey());
 		w.writeRaw(sig);
@@ -236,11 +238,16 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
 
 	private Author receivePseudonym(BdfReader r, byte[] nonce)
 			throws GeneralSecurityException, IOException {
-		// Read the name, public key and signature
+		// Read the format version, name, public key and signature
 		r.readListStart();
+		int formatVersion = (int) r.readLong();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
 		String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
+		if (name.isEmpty()) throw new FormatException();
 		byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
+		if (publicKey.length == 0) throw new FormatException();
 		byte[] sig = r.readRaw(MAX_SIGNATURE_LENGTH);
+		if (sig.length == 0) throw new FormatException();
 		r.readListEnd();
 		LOG.info("Received pseudonym");
 		// Verify the signature
@@ -249,7 +256,7 @@ class ContactExchangeTaskImpl extends Thread implements ContactExchangeTask {
 				LOG.info("Invalid signature");
 			throw new GeneralSecurityException();
 		}
-		return authorFactory.createAuthor(name, publicKey);
+		return authorFactory.createAuthor(formatVersion, name, publicKey);
 	}
 
 	private void sendTimestamp(BdfWriter w, long timestamp)
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
index e2b9ce2aa1..d7e4671717 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/db/JdbcDatabase.java
@@ -81,6 +81,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 	private static final String CREATE_LOCAL_AUTHORS =
 			"CREATE TABLE localAuthors"
 					+ " (authorId _HASH NOT NULL,"
+					+ " formatVersion INT NOT NULL,"
 					+ " name _STRING NOT NULL,"
 					+ " publicKey _BINARY NOT NULL,"
 					+ " privateKey _BINARY NOT NULL,"
@@ -91,6 +92,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			"CREATE TABLE contacts"
 					+ " (contactId _COUNTER,"
 					+ " authorId _HASH NOT NULL,"
+					+ " formatVersion INT NOT NULL,"
 					+ " name _STRING NOT NULL,"
 					+ " publicKey _BINARY NOT NULL,"
 					+ " localAuthorId _HASH NOT NULL,"
@@ -513,16 +515,18 @@ abstract class JdbcDatabase implements Database<Connection> {
 		try {
 			// Create a contact row
 			String sql = "INSERT INTO contacts"
-					+ " (authorId, name, publicKey, localAuthorId,"
+					+ " (authorId, formatVersion, name, publicKey,"
+					+ " localAuthorId,"
 					+ " verified, active)"
-					+ " VALUES (?, ?, ?, ?, ?, ?)";
+					+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, remote.getId().getBytes());
-			ps.setString(2, remote.getName());
-			ps.setBytes(3, remote.getPublicKey());
-			ps.setBytes(4, local.getBytes());
-			ps.setBoolean(5, verified);
-			ps.setBoolean(6, active);
+			ps.setInt(2, remote.getFormatVersion());
+			ps.setString(3, remote.getName());
+			ps.setBytes(4, remote.getPublicKey());
+			ps.setBytes(5, local.getBytes());
+			ps.setBoolean(6, verified);
+			ps.setBoolean(7, active);
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
@@ -590,14 +594,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		try {
 			String sql = "INSERT INTO localAuthors"
-					+ " (authorId, name, publicKey, privateKey, created)"
-					+ " VALUES (?, ?, ?, ?, ?)";
+					+ " (authorId, formatVersion, name, publicKey,"
+					+ " privateKey, created)"
+					+ " VALUES (?, ?, ?, ?, ?, ?)";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, a.getId().getBytes());
-			ps.setString(2, a.getName());
-			ps.setBytes(3, a.getPublicKey());
-			ps.setBytes(4, a.getPrivateKey());
-			ps.setLong(5, a.getTimeCreated());
+			ps.setInt(2, a.getFormatVersion());
+			ps.setString(3, a.getName());
+			ps.setBytes(4, a.getPublicKey());
+			ps.setBytes(5, a.getPrivateKey());
+			ps.setLong(6, a.getTimeCreated());
 			int affected = ps.executeUpdate();
 			if (affected != 1) throw new DbStateException();
 			ps.close();
@@ -1013,7 +1019,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey,"
+			String sql = "SELECT authorId, formatVersion, name, publicKey,"
 					+ " localAuthorId, verified, active"
 					+ " FROM contacts"
 					+ " WHERE contactId = ?";
@@ -1022,14 +1028,16 @@ abstract class JdbcDatabase implements Database<Connection> {
 			rs = ps.executeQuery();
 			if (!rs.next()) throw new DbStateException();
 			AuthorId authorId = new AuthorId(rs.getBytes(1));
-			String name = rs.getString(2);
-			byte[] publicKey = rs.getBytes(3);
-			AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
-			boolean verified = rs.getBoolean(5);
-			boolean active = rs.getBoolean(6);
+			int formatVersion = rs.getInt(2);
+			String name = rs.getString(3);
+			byte[] publicKey = rs.getBytes(4);
+			AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
+			boolean verified = rs.getBoolean(6);
+			boolean active = rs.getBoolean(7);
 			rs.close();
 			ps.close();
-			Author author = new Author(authorId, name, publicKey);
+			Author author =
+					new Author(authorId, formatVersion, name, publicKey);
 			return new Contact(c, author, localAuthorId, verified, active);
 		} catch (SQLException e) {
 			tryToClose(rs);
@@ -1044,8 +1052,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT contactId, authorId, name, publicKey,"
-					+ " localAuthorId, verified, active"
+			String sql = "SELECT contactId, authorId, formatVersion, name,"
+					+ " publicKey, localAuthorId, verified, active"
 					+ " FROM contacts";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
@@ -1053,12 +1061,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 			while (rs.next()) {
 				ContactId contactId = new ContactId(rs.getInt(1));
 				AuthorId authorId = new AuthorId(rs.getBytes(2));
-				String name = rs.getString(3);
-				byte[] publicKey = rs.getBytes(4);
-				Author author = new Author(authorId, name, publicKey);
-				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
-				boolean verified = rs.getBoolean(6);
-				boolean active = rs.getBoolean(7);
+				int formatVersion = rs.getInt(3);
+				String name = rs.getString(4);
+				byte[] publicKey = rs.getBytes(5);
+				Author author =
+						new Author(authorId, formatVersion, name, publicKey);
+				AuthorId localAuthorId = new AuthorId(rs.getBytes(6));
+				boolean verified = rs.getBoolean(7);
+				boolean active = rs.getBoolean(8);
 				contacts.add(new Contact(contactId, author, localAuthorId,
 						verified, active));
 			}
@@ -1101,7 +1111,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT contactId, name, publicKey,"
+			String sql = "SELECT contactId, formatVersion, name, publicKey,"
 					+ " localAuthorId, verified, active"
 					+ " FROM contacts"
 					+ " WHERE authorId = ?";
@@ -1111,12 +1121,14 @@ abstract class JdbcDatabase implements Database<Connection> {
 			List<Contact> contacts = new ArrayList<>();
 			while (rs.next()) {
 				ContactId c = new ContactId(rs.getInt(1));
-				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				AuthorId localAuthorId = new AuthorId(rs.getBytes(4));
-				boolean verified = rs.getBoolean(5);
-				boolean active = rs.getBoolean(6);
-				Author author = new Author(remote, name, publicKey);
+				int formatVersion = rs.getInt(2);
+				String name = rs.getString(3);
+				byte[] publicKey = rs.getBytes(4);
+				AuthorId localAuthorId = new AuthorId(rs.getBytes(5));
+				boolean verified = rs.getBoolean(6);
+				boolean active = rs.getBoolean(7);
+				Author author =
+						new Author(remote, formatVersion, name, publicKey);
 				contacts.add(new Contact(c, author, localAuthorId, verified,
 						active));
 			}
@@ -1235,19 +1247,21 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT name, publicKey, privateKey, created"
+			String sql = "SELECT formatVersion, name, publicKey,"
+					+ " privateKey, created"
 					+ " FROM localAuthors"
 					+ " WHERE authorId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, a.getBytes());
 			rs = ps.executeQuery();
 			if (!rs.next()) throw new DbStateException();
-			String name = rs.getString(1);
-			byte[] publicKey = rs.getBytes(2);
-			byte[] privateKey = rs.getBytes(3);
-			long created = rs.getLong(4);
-			LocalAuthor localAuthor = new LocalAuthor(a, name, publicKey,
-					privateKey, created);
+			int formatVersion = rs.getInt(1);
+			String name = rs.getString(2);
+			byte[] publicKey = rs.getBytes(3);
+			byte[] privateKey = rs.getBytes(4);
+			long created = rs.getLong(5);
+			LocalAuthor localAuthor = new LocalAuthor(a, formatVersion, name,
+					publicKey, privateKey, created);
 			if (rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
@@ -1265,19 +1279,21 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT authorId, name, publicKey, privateKey, created"
+			String sql = "SELECT authorId, formatVersion, name, publicKey,"
+					+ " privateKey, created"
 					+ " FROM localAuthors";
 			ps = txn.prepareStatement(sql);
 			rs = ps.executeQuery();
 			List<LocalAuthor> authors = new ArrayList<>();
 			while (rs.next()) {
 				AuthorId authorId = new AuthorId(rs.getBytes(1));
-				String name = rs.getString(2);
-				byte[] publicKey = rs.getBytes(3);
-				byte[] privateKey = rs.getBytes(4);
-				long created = rs.getLong(5);
-				authors.add(new LocalAuthor(authorId, name, publicKey,
-						privateKey, created));
+				int formatVersion = rs.getInt(2);
+				String name = rs.getString(3);
+				byte[] publicKey = rs.getBytes(4);
+				byte[] privateKey = rs.getBytes(5);
+				long created = rs.getLong(6);
+				authors.add(new LocalAuthor(authorId, formatVersion, name,
+						publicKey, privateKey, created));
 			}
 			rs.close();
 			ps.close();
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorFactoryImpl.java
index 5d4dc0de67..bbd2ae65cb 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorFactoryImpl.java
@@ -1,61 +1,65 @@
 package org.briarproject.bramble.identity;
 
 import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.data.BdfWriter;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.system.Clock;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
+import org.briarproject.bramble.util.ByteUtils;
+import org.briarproject.bramble.util.StringUtils;
 
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
+import static org.briarproject.bramble.api.identity.AuthorId.LABEL;
+import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
+
 @Immutable
 @NotNullByDefault
 class AuthorFactoryImpl implements AuthorFactory {
 
 	private final CryptoComponent crypto;
-	private final BdfWriterFactory bdfWriterFactory;
 	private final Clock clock;
 
 	@Inject
-	AuthorFactoryImpl(CryptoComponent crypto, BdfWriterFactory bdfWriterFactory,
-			Clock clock) {
+	AuthorFactoryImpl(CryptoComponent crypto, Clock clock) {
 		this.crypto = crypto;
-		this.bdfWriterFactory = bdfWriterFactory;
 		this.clock = clock;
 	}
 
 	@Override
 	public Author createAuthor(String name, byte[] publicKey) {
-		return new Author(getId(name, publicKey), name, publicKey);
+		return createAuthor(FORMAT_VERSION, name, publicKey);
+	}
+
+	@Override
+	public Author createAuthor(int formatVersion, String name,
+			byte[] publicKey) {
+		AuthorId id = getId(formatVersion, name, publicKey);
+		return new Author(id, formatVersion, name, publicKey);
 	}
 
 	@Override
 	public LocalAuthor createLocalAuthor(String name, byte[] publicKey,
 			byte[] privateKey) {
-		return new LocalAuthor(getId(name, publicKey), name, publicKey,
-				privateKey, clock.currentTimeMillis());
+		return createLocalAuthor(FORMAT_VERSION, name, publicKey, privateKey);
+	}
+
+	@Override
+	public LocalAuthor createLocalAuthor(int formatVersion, String name,
+			byte[] publicKey, byte[] privateKey) {
+		AuthorId id = getId(formatVersion, name, publicKey);
+		return new LocalAuthor(id, formatVersion, name, publicKey, privateKey,
+				clock.currentTimeMillis());
 	}
 
-	private AuthorId getId(String name, byte[] publicKey) {
-		ByteArrayOutputStream out = new ByteArrayOutputStream();
-		BdfWriter w = bdfWriterFactory.createWriter(out);
-		try {
-			w.writeListStart();
-			w.writeString(name);
-			w.writeRaw(publicKey);
-			w.writeListEnd();
-		} catch (IOException e) {
-			// Shouldn't happen with ByteArrayOutputStream
-			throw new RuntimeException(e);
-		}
-		return new AuthorId(crypto.hash(AuthorId.LABEL, out.toByteArray()));
+	private AuthorId getId(int formatVersion, String name, byte[] publicKey) {
+		byte[] formatVersionBytes = new byte[INT_32_BYTES];
+		ByteUtils.writeUint32(formatVersion, formatVersionBytes, 0);
+		return new AuthorId(crypto.hash(LABEL, formatVersionBytes,
+				StringUtils.toUtf8(name), publicKey));
 	}
 }
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorReader.java b/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorReader.java
deleted file mode 100644
index fbef759389..0000000000
--- a/bramble-core/src/main/java/org/briarproject/bramble/identity/AuthorReader.java
+++ /dev/null
@@ -1,36 +0,0 @@
-package org.briarproject.bramble.identity;
-
-import org.briarproject.bramble.api.FormatException;
-import org.briarproject.bramble.api.data.BdfReader;
-import org.briarproject.bramble.api.data.ObjectReader;
-import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
-import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-
-import java.io.IOException;
-
-import javax.annotation.concurrent.Immutable;
-
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-
-@Immutable
-@NotNullByDefault
-class AuthorReader implements ObjectReader<Author> {
-
-	private final AuthorFactory authorFactory;
-
-	AuthorReader(AuthorFactory authorFactory) {
-		this.authorFactory = authorFactory;
-	}
-
-	@Override
-	public Author readObject(BdfReader r) throws IOException {
-		r.readListStart();
-		String name = r.readString(MAX_AUTHOR_NAME_LENGTH);
-		if (name.length() == 0) throw new FormatException();
-		byte[] publicKey = r.readRaw(MAX_PUBLIC_KEY_LENGTH);
-		r.readListEnd();
-		return authorFactory.createAuthor(name, publicKey);
-	}
-}
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityModule.java b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityModule.java
index 3fe6b9ab26..950c0f383b 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityModule.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/identity/IdentityModule.java
@@ -1,13 +1,7 @@
 package org.briarproject.bramble.identity;
 
-import org.briarproject.bramble.api.crypto.CryptoComponent;
-import org.briarproject.bramble.api.data.BdfWriterFactory;
-import org.briarproject.bramble.api.data.ObjectReader;
-import org.briarproject.bramble.api.db.DatabaseComponent;
-import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.identity.IdentityManager;
-import org.briarproject.bramble.api.system.Clock;
 
 import javax.inject.Inject;
 import javax.inject.Singleton;
@@ -24,19 +18,14 @@ public class IdentityModule {
 	}
 
 	@Provides
-	AuthorFactory provideAuthorFactory(CryptoComponent crypto,
-			BdfWriterFactory bdfWriterFactory, Clock clock) {
-		return new AuthorFactoryImpl(crypto, bdfWriterFactory, clock);
+	AuthorFactory provideAuthorFactory(AuthorFactoryImpl authorFactory) {
+		return authorFactory;
 	}
 
 	@Provides
 	@Singleton
-	IdentityManager provideIdentityModule(DatabaseComponent db) {
-		return new IdentityManagerImpl(db);
-	}
-
-	@Provides
-	ObjectReader<Author> provideAuthorReader(AuthorFactory authorFactory) {
-		return new AuthorReader(authorFactory);
+	IdentityManager provideIdentityManager(
+			IdentityManagerImpl identityManager) {
+		return identityManager;
 	}
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java
index fddf10b363..4ffe9e5326 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/client/ClientHelperImplTest.java
@@ -15,6 +15,8 @@ import org.briarproject.bramble.api.data.MetadataParser;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.Metadata;
 import org.briarproject.bramble.api.db.Transaction;
+import org.briarproject.bramble.api.identity.Author;
+import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageFactory;
@@ -31,9 +33,14 @@ import java.security.GeneralSecurityException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Random;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.fail;
@@ -54,7 +61,8 @@ public class ClientHelperImplTest extends BrambleTestCase {
 			context.mock(MetadataEncoder.class);
 	private final CryptoComponent cryptoComponent =
 			context.mock(CryptoComponent.class);
-	private final ClientHelper clientHelper;
+	private final AuthorFactory authorFactory =
+			context.mock(AuthorFactory.class);
 
 	private final GroupId groupId = new GroupId(getRandomId());
 	private final BdfDictionary dictionary = new BdfDictionary();
@@ -66,17 +74,15 @@ public class ClientHelperImplTest extends BrambleTestCase {
 	private final Metadata metadata = new Metadata();
 	private final BdfList list = BdfList.of("Sign this!", getRandomBytes(42));
 	private final String label = StringUtils.getRandomString(5);
+	private final Author author = getAuthor();
 
-	public ClientHelperImplTest() {
-		clientHelper =
-				new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
-						bdfWriterFactory, metadataParser, metadataEncoder,
-						cryptoComponent);
-	}
+	private final ClientHelper clientHelper = new ClientHelperImpl(db,
+			messageFactory, bdfReaderFactory, bdfWriterFactory, metadataParser,
+			metadataEncoder, cryptoComponent, authorFactory);
 
 	@Test
 	public void testAddLocalMessage() throws Exception {
-		boolean shared = true;
+		boolean shared = new Random().nextBoolean();
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
@@ -180,8 +186,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
 			oneOf(db).endTransaction(txn);
 		}});
 
-		assertEquals(map,
-				clientHelper.getMessageMetadataAsDictionary(groupId));
+		assertEquals(map, clientHelper.getMessageMetadataAsDictionary(groupId));
 		context.assertIsSatisfied();
 	}
 
@@ -318,8 +323,7 @@ public class ClientHelperImplTest extends BrambleTestCase {
 		}});
 
 		try {
-			clientHelper
-					.verifySignature(label, rawMessage, publicKey, list);
+			clientHelper.verifySignature(label, rawMessage, publicKey, list);
 			fail();
 		} catch (GeneralSecurityException e) {
 			// expected
@@ -327,6 +331,154 @@ public class ClientHelperImplTest extends BrambleTestCase {
 		}
 	}
 
+	@Test
+	public void testAcceptsValidAuthor() throws Exception {
+		BdfList authorList = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				author.getPublicKey()
+		);
+
+		context.checking(new Expectations() {{
+			oneOf(authorFactory).createAuthor(author.getFormatVersion(),
+					author.getName(), author.getPublicKey());
+			will(returnValue(author));
+		}});
+
+		assertEquals(author, clientHelper.parseAndValidateAuthor(authorList));
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooShortAuthor() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				author.getName()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsTooLongAuthor() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				author.getPublicKey(),
+				"foo"
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithNullFormatVersion() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				null,
+				author.getName(),
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithNonIntegerFormatVersion()
+			throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				"foo",
+				author.getName(),
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithUnknownFormatVersion() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion() + 1,
+				author.getName(),
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithTooShortName() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				"",
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithTooLongName() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				getRandomString(MAX_AUTHOR_NAME_LENGTH + 1),
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithNullName() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				null,
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithNonStringName() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				getRandomBytes(5),
+				author.getPublicKey()
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithTooShortPublicKey() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				new byte[0]
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithTooLongPublicKey() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1)
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithNullPublicKey() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				null
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsAuthorWithNonRawPublicKey() throws Exception {
+		BdfList invalidAuthor = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				"foo"
+		);
+		clientHelper.parseAndValidateAuthor(invalidAuthor);
+	}
+
 	private byte[] expectToByteArray(BdfList list) throws Exception {
 		BdfWriter bdfWriter = context.mock(BdfWriter.class);
 
@@ -352,5 +504,4 @@ public class ClientHelperImplTest extends BrambleTestCase {
 			will(returnValue(eof));
 		}});
 	}
-
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java
index 9ded370dd8..50c8305e90 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/contact/ContactManagerImplTest.java
@@ -18,8 +18,9 @@ import org.junit.Test;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Random;
 
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.junit.Assert.assertEquals;
@@ -32,9 +33,7 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
 	private final KeyManager keyManager = context.mock(KeyManager.class);
 	private final ContactManager contactManager;
 	private final ContactId contactId = new ContactId(42);
-	private final Author remote =
-			new Author(new AuthorId(getRandomId()), "remote",
-					getRandomBytes(42));
+	private final Author remote = getAuthor();
 	private final AuthorId local = new AuthorId(getRandomId());
 	private final boolean verified = false, active = true;
 	private final Contact contact =
@@ -47,8 +46,8 @@ public class ContactManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testAddContact() throws Exception {
 		SecretKey master = getSecretKey();
-		long timestamp = 42;
-		boolean alice = true;
+		long timestamp = System.currentTimeMillis();
+		boolean alice = new Random().nextBoolean();
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
index ba6bec9416..03905099f1 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/DatabaseComponentImplTest.java
@@ -17,7 +17,6 @@ import org.briarproject.bramble.api.db.NoSuchTransportException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.event.EventBus;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.identity.event.LocalAuthorAddedEvent;
 import org.briarproject.bramble.api.identity.event.LocalAuthorRemovedEvent;
@@ -49,7 +48,6 @@ import org.briarproject.bramble.api.transport.OutgoingKeys;
 import org.briarproject.bramble.api.transport.TransportKeys;
 import org.briarproject.bramble.test.BrambleMockTestCase;
 import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.jmock.Expectations;
 import org.junit.Test;
 
@@ -59,7 +57,6 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -68,6 +65,9 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
 import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
 import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
 import static org.briarproject.bramble.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -85,9 +85,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 	private final ClientId clientId;
 	private final GroupId groupId;
 	private final Group group;
-	private final AuthorId authorId;
 	private final Author author;
-	private final AuthorId localAuthorId;
 	private final LocalAuthor localAuthor;
 	private final MessageId messageId, messageId1;
 	private final int size;
@@ -100,18 +98,15 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 	private final Contact contact;
 
 	public DatabaseComponentImplTest() {
-		clientId = new ClientId(StringUtils.getRandomString(5));
+		clientId = new ClientId(getRandomString(123));
 		groupId = new GroupId(TestUtils.getRandomId());
 		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		group = new Group(groupId, clientId, descriptor);
-		authorId = new AuthorId(TestUtils.getRandomId());
-		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
-		localAuthorId = new AuthorId(TestUtils.getRandomId());
-		long timestamp = System.currentTimeMillis();
-		localAuthor = new LocalAuthor(localAuthorId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
+		author = getAuthor();
+		localAuthor = getLocalAuthor();
 		messageId = new MessageId(TestUtils.getRandomId());
 		messageId1 = new MessageId(TestUtils.getRandomId());
+		long timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
 		message = new Message(messageId, groupId, timestamp, raw);
@@ -120,7 +115,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		transportId = new TransportId("id");
 		maxLatency = Integer.MAX_VALUE;
 		contactId = new ContactId(234);
-		contact = new Contact(contactId, author, localAuthorId, true, true);
+		contact = new Contact(contactId, author, localAuthor.getId(),
+				true, true);
 	}
 
 	private DatabaseComponent createDatabaseComponent(Database<Object> database,
@@ -142,18 +138,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
 			// registerLocalAuthor()
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
 			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(true));
-			oneOf(database).containsLocalAuthor(txn, authorId);
+			oneOf(database).containsLocalAuthor(txn, author.getId());
 			will(returnValue(false));
-			oneOf(database).containsContact(txn, authorId, localAuthorId);
+			oneOf(database).containsContact(txn, author.getId(),
+					localAuthor.getId());
 			will(returnValue(false));
-			oneOf(database).addContact(txn, author, localAuthorId, true, true);
+			oneOf(database).addContact(txn, author, localAuthor.getId(),
+					true, true);
 			will(returnValue(contactId));
 			oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
 			oneOf(eventBus).broadcast(with(any(
@@ -187,9 +185,9 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			oneOf(database).removeContact(txn, contactId);
 			oneOf(eventBus).broadcast(with(any(ContactRemovedEvent.class)));
 			// removeLocalAuthor()
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(true));
-			oneOf(database).removeLocalAuthor(txn, localAuthorId);
+			oneOf(database).removeLocalAuthor(txn, localAuthor.getId());
 			oneOf(eventBus).broadcast(with(any(LocalAuthorRemovedEvent.class)));
 			// endTransaction()
 			oneOf(database).commitTransaction(txn);
@@ -203,9 +201,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(transaction, localAuthor);
-			assertEquals(contactId,
-					db.addContact(transaction, author, localAuthorId, true,
-							true));
+			assertEquals(contactId, db.addContact(transaction, author,
+					localAuthor.getId(), true, true));
 			assertEquals(Collections.singletonList(contact),
 					db.getContacts(transaction));
 			db.addGroup(transaction, group); // First time - listeners called
@@ -214,7 +211,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 					db.getGroups(transaction, clientId));
 			db.removeGroup(transaction, group);
 			db.removeContact(transaction, contactId);
-			db.removeLocalAuthor(transaction, localAuthorId);
+			db.removeLocalAuthor(transaction, localAuthor.getId());
 			db.commitTransaction(transaction);
 		} finally {
 			db.endTransaction(transaction);
@@ -487,7 +484,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			// Check whether the pseudonym is in the DB (which it's not)
 			exactly(3).of(database).startTransaction();
 			will(returnValue(txn));
-			exactly(3).of(database).containsLocalAuthor(txn, localAuthorId);
+			exactly(3).of(database).containsLocalAuthor(txn,
+					localAuthor.getId());
 			will(returnValue(false));
 			exactly(3).of(database).abortTransaction(txn);
 		}});
@@ -496,7 +494,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 
 		Transaction transaction = db.startTransaction(false);
 		try {
-			db.addContact(transaction, author, localAuthorId, true, true);
+			db.addContact(transaction, author, localAuthor.getId(), true, true);
 			fail();
 		} catch (NoSuchLocalAuthorException expected) {
 			// Expected
@@ -506,7 +504,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 
 		transaction = db.startTransaction(false);
 		try {
-			db.getLocalAuthor(transaction, localAuthorId);
+			db.getLocalAuthor(transaction, localAuthor.getId());
 			fail();
 		} catch (NoSuchLocalAuthorException expected) {
 			// Expected
@@ -516,7 +514,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 
 		transaction = db.startTransaction(false);
 		try {
-			db.removeLocalAuthor(transaction, localAuthorId);
+			db.removeLocalAuthor(transaction, localAuthor.getId());
 			fail();
 		} catch (NoSuchLocalAuthorException expected) {
 			// Expected
@@ -759,18 +757,20 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
 			// registerLocalAuthor()
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(false));
 			oneOf(database).addLocalAuthor(txn, localAuthor);
 			oneOf(eventBus).broadcast(with(any(LocalAuthorAddedEvent.class)));
 			// addContact()
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(true));
-			oneOf(database).containsLocalAuthor(txn, authorId);
+			oneOf(database).containsLocalAuthor(txn, author.getId());
 			will(returnValue(false));
-			oneOf(database).containsContact(txn, authorId, localAuthorId);
+			oneOf(database).containsContact(txn, author.getId(),
+					localAuthor.getId());
 			will(returnValue(false));
-			oneOf(database).addContact(txn, author, localAuthorId, true, true);
+			oneOf(database).addContact(txn, author, localAuthor.getId(),
+					true, true);
 			will(returnValue(contactId));
 			oneOf(eventBus).broadcast(with(any(ContactAddedEvent.class)));
 			oneOf(eventBus).broadcast(with(any(
@@ -792,9 +792,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		Transaction transaction = db.startTransaction(false);
 		try {
 			db.addLocalAuthor(transaction, localAuthor);
-			assertEquals(contactId,
-					db.addContact(transaction, author, localAuthorId, true,
-							true));
+			assertEquals(contactId, db.addContact(transaction, author,
+					localAuthor.getId(), true, true));
 			db.commitTransaction(transaction);
 		} finally {
 			db.endTransaction(transaction);
@@ -1406,10 +1405,10 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(true));
 			// Contact is a local identity
-			oneOf(database).containsLocalAuthor(txn, authorId);
+			oneOf(database).containsLocalAuthor(txn, author.getId());
 			will(returnValue(true));
 			oneOf(database).abortTransaction(txn);
 		}});
@@ -1419,7 +1418,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 
 		Transaction transaction = db.startTransaction(false);
 		try {
-			db.addContact(transaction, author, localAuthorId, true, true);
+			db.addContact(transaction, author, localAuthor.getId(), true, true);
 			fail();
 		} catch (ContactExistsException expected) {
 			// Expected
@@ -1433,12 +1432,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 		context.checking(new Expectations() {{
 			oneOf(database).startTransaction();
 			will(returnValue(txn));
-			oneOf(database).containsLocalAuthor(txn, localAuthorId);
+			oneOf(database).containsLocalAuthor(txn, localAuthor.getId());
 			will(returnValue(true));
-			oneOf(database).containsLocalAuthor(txn, authorId);
+			oneOf(database).containsLocalAuthor(txn, author.getId());
 			will(returnValue(false));
 			// Contact already exists for this local identity
-			oneOf(database).containsContact(txn, authorId, localAuthorId);
+			oneOf(database).containsContact(txn, author.getId(),
+					localAuthor.getId());
 			will(returnValue(true));
 			oneOf(database).abortTransaction(txn);
 		}});
@@ -1448,7 +1448,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
 
 		Transaction transaction = db.startTransaction(false);
 		try {
-			db.addContact(transaction, author, localAuthorId, true, true);
+			db.addContact(transaction, author, localAuthor.getId(), true, true);
 			fail();
 		} catch (ContactExistsException expected) {
 			// Expected
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
index 2812eda45a..a09bc934f4 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/db/JdbcDatabaseTest.java
@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.db.DatabaseConfig;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Metadata;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.bramble.api.settings.Settings;
@@ -26,7 +25,6 @@ import org.briarproject.bramble.system.SystemClock;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.bramble.test.TestDatabaseConfig;
 import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -46,7 +44,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
 
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.briarproject.bramble.api.db.Metadata.REMOVE;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
@@ -56,6 +53,11 @@ import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERE
 import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -75,7 +77,6 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 	private final ClientId clientId;
 	private final Group group;
 	private final Author author;
-	private final AuthorId localAuthorId;
 	private final LocalAuthor localAuthor;
 	private final MessageId messageId;
 	private final long timestamp;
@@ -86,19 +87,16 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 	private final ContactId contactId;
 
 	JdbcDatabaseTest() throws Exception {
-		groupId = new GroupId(TestUtils.getRandomId());
-		clientId = new ClientId(StringUtils.getRandomString(5));
+		groupId = new GroupId(getRandomId());
+		clientId = new ClientId(getRandomString(123));
 		byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
 		group = new Group(groupId, clientId, descriptor);
-		AuthorId authorId = new AuthorId(TestUtils.getRandomId());
-		author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
-		localAuthorId = new AuthorId(TestUtils.getRandomId());
+		author = getAuthor();
+		localAuthor = getLocalAuthor();
+		messageId = new MessageId(getRandomId());
 		timestamp = System.currentTimeMillis();
-		localAuthor = new LocalAuthor(localAuthorId, "Bob",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
-		messageId = new MessageId(TestUtils.getRandomId());
 		size = 1234;
-		raw = TestUtils.getRandomBytes(size);
+		raw = getRandomBytes(size);
 		message = new Message(messageId, groupId, timestamp, raw);
 		transportId = new TransportId("id");
 		contactId = new ContactId(1);
@@ -119,7 +117,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		Connection txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsGroup(txn, groupId));
@@ -182,7 +180,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and a shared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -220,7 +218,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and a shared but unvalidated message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -266,7 +264,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, an invisible group and a shared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addMessage(txn, message, DELIVERED, true);
@@ -318,7 +316,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and an unshared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -350,7 +348,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and a shared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -377,13 +375,13 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact and a visible group
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, false);
 
 		// Add some messages to ack
-		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId1 = new MessageId(getRandomId());
 		Message message1 = new Message(messageId1, groupId, timestamp, raw);
 		db.addMessage(txn, message, DELIVERED, true);
 		db.addStatus(txn, contactId, messageId, false, true);
@@ -414,7 +412,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and a shared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -572,7 +570,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact and a shared group
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -592,7 +590,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 
 		// The group is not in the database
@@ -610,7 +608,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a group and a message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addMessage(txn, message, DELIVERED, true);
@@ -630,7 +628,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact and a group
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 
@@ -680,7 +678,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add the contact, the transport and the transport keys
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addTransport(txn, transportId, 123);
 		db.addTransportKeys(txn, contactId, keys);
@@ -742,7 +740,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add the contact, transport and transport keys
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addTransport(txn, transportId, 123);
 		db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
@@ -778,7 +776,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add the contact, transport and transport keys
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addTransport(txn, transportId, 123);
 		db.updateTransportKeys(txn, Collections.singletonMap(contactId, keys));
@@ -813,7 +811,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		db.addLocalAuthor(txn, localAuthor);
 
 		// Add a contact associated with the local author
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 
 		// Ensure contact is returned from database by Author ID
@@ -838,18 +836,19 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a local author - no contacts should be associated
 		db.addLocalAuthor(txn, localAuthor);
-		Collection<ContactId> contacts = db.getContacts(txn, localAuthorId);
+		Collection<ContactId> contacts =
+				db.getContacts(txn, localAuthor.getId());
 		assertEquals(Collections.emptyList(), contacts);
 
 		// Add a contact associated with the local author
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
-		contacts = db.getContacts(txn, localAuthorId);
+		contacts = db.getContacts(txn, localAuthor.getId());
 		assertEquals(Collections.singletonList(contactId), contacts);
 
 		// Remove the local author - the contact should be removed
-		db.removeLocalAuthor(txn, localAuthorId);
-		contacts = db.getContacts(txn, localAuthorId);
+		db.removeLocalAuthor(txn, localAuthor.getId());
+		contacts = db.getContacts(txn, localAuthor.getId());
 		assertEquals(Collections.emptyList(), contacts);
 		assertFalse(db.containsContact(txn, contactId));
 
@@ -864,14 +863,14 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact - initially there should be no offered messages
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		assertEquals(0, db.countOfferedMessages(txn, contactId));
 
 		// Add some offered messages and count them
 		List<MessageId> ids = new ArrayList<>();
 		for (int i = 0; i < 10; i++) {
-			MessageId m = new MessageId(TestUtils.getRandomId());
+			MessageId m = new MessageId(getRandomId());
 			db.addOfferedMessage(txn, contactId, m);
 			ids.add(m);
 		}
@@ -1056,7 +1055,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 	@Test
 	public void testMetadataQueries() throws Exception {
-		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId1 = new MessageId(getRandomId());
 		Message message1 = new Message(messageId1, groupId, timestamp, raw);
 
 		Database<Connection> db = open(false);
@@ -1160,7 +1159,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 	@Test
 	public void testMetadataQueriesOnlyForDeliveredMessages() throws Exception {
-		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId1 = new MessageId(getRandomId());
 		Message message1 = new Message(messageId1, groupId, timestamp, raw);
 
 		Database<Connection> db = open(false);
@@ -1231,10 +1230,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 	@Test
 	public void testMessageDependencies() throws Exception {
-		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
-		MessageId messageId2 = new MessageId(TestUtils.getRandomId());
-		MessageId messageId3 = new MessageId(TestUtils.getRandomId());
-		MessageId messageId4 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId1 = new MessageId(getRandomId());
+		MessageId messageId2 = new MessageId(getRandomId());
+		MessageId messageId3 = new MessageId(getRandomId());
+		MessageId messageId4 = new MessageId(getRandomId());
 		Message message1 = new Message(messageId1, groupId, timestamp, raw);
 		Message message2 = new Message(messageId2, groupId, timestamp, raw);
 
@@ -1315,21 +1314,21 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 		db.addMessage(txn, message, PENDING, true);
 
 		// Add a second group
-		GroupId groupId1 = new GroupId(TestUtils.getRandomId());
+		GroupId groupId1 = new GroupId(getRandomId());
 		Group group1 = new Group(groupId1, clientId,
-				TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
+				getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
 		db.addGroup(txn, group1);
 
 		// Add a message to the second group
-		MessageId messageId1 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId1 = new MessageId(getRandomId());
 		Message message1 = new Message(messageId1, groupId1, timestamp, raw);
 		db.addMessage(txn, message1, DELIVERED, true);
 
 		// Create an ID for a missing message
-		MessageId messageId2 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId2 = new MessageId(getRandomId());
 
 		// Add another message to the first group
-		MessageId messageId3 = new MessageId(TestUtils.getRandomId());
+		MessageId messageId3 = new MessageId(getRandomId());
 		Message message3 = new Message(messageId3, groupId, timestamp, raw);
 		db.addMessage(txn, message3, DELIVERED, true);
 
@@ -1364,10 +1363,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 	@Test
 	public void testGetPendingMessagesForDelivery() throws Exception {
-		MessageId mId1 = new MessageId(TestUtils.getRandomId());
-		MessageId mId2 = new MessageId(TestUtils.getRandomId());
-		MessageId mId3 = new MessageId(TestUtils.getRandomId());
-		MessageId mId4 = new MessageId(TestUtils.getRandomId());
+		MessageId mId1 = new MessageId(getRandomId());
+		MessageId mId2 = new MessageId(getRandomId());
+		MessageId mId3 = new MessageId(getRandomId());
+		MessageId mId4 = new MessageId(getRandomId());
 		Message m1 = new Message(mId1, groupId, timestamp, raw);
 		Message m2 = new Message(mId2, groupId, timestamp, raw);
 		Message m3 = new Message(mId3, groupId, timestamp, raw);
@@ -1401,10 +1400,10 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 	@Test
 	public void testGetMessagesToShare() throws Exception {
-		MessageId mId1 = new MessageId(TestUtils.getRandomId());
-		MessageId mId2 = new MessageId(TestUtils.getRandomId());
-		MessageId mId3 = new MessageId(TestUtils.getRandomId());
-		MessageId mId4 = new MessageId(TestUtils.getRandomId());
+		MessageId mId1 = new MessageId(getRandomId());
+		MessageId mId2 = new MessageId(getRandomId());
+		MessageId mId3 = new MessageId(getRandomId());
+		MessageId mId4 = new MessageId(getRandomId());
 		Message m1 = new Message(mId1, groupId, timestamp, raw);
 		Message m2 = new Message(mId2, groupId, timestamp, raw);
 		Message m3 = new Message(mId3, groupId, timestamp, raw);
@@ -1443,7 +1442,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and a shared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -1512,9 +1511,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 	@Test
 	public void testDifferentLocalAuthorsCanHaveTheSameContact()
 			throws Exception {
-		AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
-		LocalAuthor localAuthor1 = new LocalAuthor(localAuthorId1, "Carol",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
+		LocalAuthor localAuthor1 = getLocalAuthor();
 
 		Database<Connection> db = open(false);
 		Connection txn = db.startTransaction();
@@ -1525,15 +1522,15 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add the same contact for each local author
 		ContactId contactId =
-				db.addContact(txn, author, localAuthorId, true, true);
+				db.addContact(txn, author, localAuthor.getId(), true, true);
 		ContactId contactId1 =
-				db.addContact(txn, author, localAuthorId1, true, true);
+				db.addContact(txn, author, localAuthor1.getId(), true, true);
 
 		// The contacts should be distinct
 		assertNotEquals(contactId, contactId1);
 		assertEquals(2, db.getContacts(txn).size());
-		assertEquals(1, db.getContacts(txn, localAuthorId).size());
-		assertEquals(1, db.getContacts(txn, localAuthorId1).size());
+		assertEquals(1, db.getContacts(txn, localAuthor.getId()).size());
+		assertEquals(1, db.getContacts(txn, localAuthor1.getId()).size());
 
 		db.commitTransaction(txn);
 		db.close();
@@ -1546,7 +1543,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact, a shared group and a shared message
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 		db.addGroup(txn, group);
 		db.addGroupVisibility(txn, contactId, groupId, true);
@@ -1592,7 +1589,7 @@ public abstract class JdbcDatabaseTest extends BrambleTestCase {
 
 		// Add a contact
 		db.addLocalAuthor(txn, localAuthor);
-		assertEquals(contactId, db.addContact(txn, author, localAuthorId,
+		assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
 				true, true));
 
 		// The contact should be active
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
index 2a6e56ee01..e24499ba7f 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/identity/IdentityManagerImplTest.java
@@ -10,8 +10,6 @@ import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.test.BrambleMockTestCase;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.jmock.Expectations;
 import org.junit.Test;
 
@@ -23,6 +21,8 @@ import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
 import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
 import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.junit.Assert.assertEquals;
 
 public class IdentityManagerImplTest extends BrambleMockTestCase {
@@ -30,11 +30,7 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 	private final IdentityManager identityManager;
 	private final DatabaseComponent db = context.mock(DatabaseComponent.class);
 	private final Transaction txn = new Transaction(null, false);
-	private final LocalAuthor localAuthor =
-			new LocalAuthor(new AuthorId(TestUtils.getRandomId()),
-					StringUtils.getRandomString(8),
-					TestUtils.getRandomBytes(42), TestUtils.getRandomBytes(42),
-					0);
+	private final LocalAuthor localAuthor = getLocalAuthor();
 	private final Collection<LocalAuthor> localAuthors =
 			Collections.singletonList(localAuthor);
 
@@ -80,7 +76,8 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 
 	@Test
 	public void testGetAuthorStatus() throws DbException {
-		AuthorId authorId = new AuthorId(TestUtils.getRandomId());
+		Author author = getAuthor();
+		AuthorId authorId = author.getId();
 		Collection<Contact> contacts = new ArrayList<>();
 
 		context.checking(new Expectations() {{
@@ -95,20 +92,16 @@ public class IdentityManagerImplTest extends BrambleMockTestCase {
 		assertEquals(UNKNOWN, identityManager.getAuthorStatus(authorId));
 
 		// add one unverified contact
-		Author author = new Author(authorId, StringUtils.getRandomString(8),
-				TestUtils.getRandomBytes(42));
-		Contact contact =
-				new Contact(new ContactId(1), author, localAuthor.getId(),
-						false, true);
+		Contact contact = new Contact(new ContactId(1), author,
+				localAuthor.getId(), false, true);
 		contacts.add(contact);
 
 		checkAuthorStatusContext(authorId, contacts);
 		assertEquals(UNVERIFIED, identityManager.getAuthorStatus(authorId));
 
 		// add one verified contact
-		Contact contact2 =
-				new Contact(new ContactId(1), author, localAuthor.getId(),
-						true, true);
+		Contact contact2 = new Contact(new ContactId(1), author,
+				localAuthor.getId(), true, true);
 		contacts.add(contact2);
 
 		checkAuthorStatusContext(authorId, contacts);
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java
index 7f4868f49a..d122499bb1 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/properties/TransportPropertyManagerImplTest.java
@@ -11,8 +11,6 @@ import org.briarproject.bramble.api.data.MetadataParser;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.Metadata;
 import org.briarproject.bramble.api.db.Transaction;
-import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.bramble.api.properties.TransportProperties;
@@ -31,16 +29,15 @@ import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_ID;
 import static org.briarproject.bramble.api.properties.TransportPropertyManager.CLIENT_VERSION;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
-import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -609,22 +606,10 @@ public class TransportPropertyManagerImplTest extends BrambleMockTestCase {
 		return new Group(g, CLIENT_ID, descriptor);
 	}
 
-	private LocalAuthor getLocalAuthor() {
-		AuthorId id = new AuthorId(getRandomId());
-		String name = getRandomString(MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		byte[] privateKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		long created = System.currentTimeMillis();
-		return new LocalAuthor(id, name, publicKey, privateKey, created);
-	}
-
 	private Contact getContact(boolean active) {
 		ContactId c = new ContactId(nextContactId++);
-		AuthorId a = new AuthorId(getRandomId());
-		String name = getRandomString(MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		return new Contact(c, new Author(a, name, publicKey),
-				localAuthor.getId(), true, active);
+		return new Contact(c, getAuthor(), localAuthor.getId(),
+				true, active);
 	}
 
 	private Message getMessage(GroupId g, long timestamp) {
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java b/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java
index 5f29d02034..1629e53359 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/test/ValidatorTestCase.java
@@ -9,7 +9,10 @@ import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.util.StringUtils;
+
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 
 public abstract class ValidatorTestCase extends BrambleMockTestCase {
 
@@ -21,16 +24,14 @@ public abstract class ValidatorTestCase extends BrambleMockTestCase {
 	protected final AuthorFactory authorFactory =
 			context.mock(AuthorFactory.class);
 
-	protected final MessageId messageId =
-			new MessageId(TestUtils.getRandomId());
-	protected final GroupId groupId = new GroupId(TestUtils.getRandomId());
+	protected final MessageId messageId = new MessageId(getRandomId());
+	protected final GroupId groupId = new GroupId(getRandomId());
 	protected final long timestamp = 1234567890 * 1000L;
-	protected final byte[] raw = TestUtils.getRandomBytes(123);
+	protected final byte[] raw = getRandomBytes(123);
 	protected final Message message =
 			new Message(messageId, groupId, timestamp, raw);
-	protected final ClientId clientId =
-			new ClientId(StringUtils.getRandomString(123));
-	protected final byte[] descriptor = TestUtils.getRandomBytes(123);
+	protected final ClientId clientId = new ClientId(getRandomString(123));
+	protected final byte[] descriptor = getRandomBytes(123);
 	protected final Group group = new Group(groupId, clientId, descriptor);
 
 }
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
index ade43e1448..edf073a4f6 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
@@ -23,8 +23,10 @@ import org.junit.Test;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Random;
 
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
@@ -59,9 +61,7 @@ public class KeyManagerImplTest extends BrambleTestCase {
 	@Before
 	public void testStartService() throws Exception {
 		Transaction txn = new Transaction(null, false);
-		AuthorId remoteAuthorId = new AuthorId(getRandomId());
-		Author remoteAuthor = new Author(remoteAuthorId, "author",
-				getRandomBytes(42));
+		Author remoteAuthor = getAuthor();
 		AuthorId localAuthorId = new AuthorId(getRandomId());
 		Collection<Contact> contacts = new ArrayList<>();
 		contacts.add(new Contact(contactId, remoteAuthor, localAuthorId, true,
@@ -101,12 +101,12 @@ public class KeyManagerImplTest extends BrambleTestCase {
 	@Test
 	public void testAddContact() throws Exception {
 		SecretKey secretKey = getSecretKey();
-		long timestamp = 42L;
-		boolean alice =  true;
+		long timestamp = System.currentTimeMillis();
+		boolean alice = new Random().nextBoolean();
 
 		context.checking(new Expectations() {{
-			oneOf(transportKeyManager)
-					.addContact(txn, contactId, secretKey, timestamp, alice);
+			oneOf(transportKeyManager).addContact(txn, contactId, secretKey,
+					timestamp, alice);
 		}});
 
 		keyManager.addContact(txn, contactId, secretKey, timestamp, alice);
@@ -121,8 +121,8 @@ public class KeyManagerImplTest extends BrambleTestCase {
 
 	@Test
 	public void testGetStreamContextForUnknownTransport() throws Exception {
-		assertEquals(null, keyManager
-				.getStreamContext(contactId, unknownTransportId));
+		assertEquals(null,
+				keyManager.getStreamContext(contactId, unknownTransportId));
 	}
 
 	@Test
diff --git a/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java b/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java
index cee3b710f0..bf18e7ad0e 100644
--- a/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java
+++ b/briar-android/src/test/java/org/briarproject/briar/android/forum/ForumActivityTest.java
@@ -6,9 +6,7 @@ import junit.framework.Assert;
 
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.bramble.test.TestUtils;
 import org.briarproject.briar.android.TestBriarApplication;
 import org.briarproject.briar.android.controller.handler.UiResultExceptionHandler;
 import org.briarproject.briar.android.threaded.ThreadItemAdapter;
@@ -29,7 +27,10 @@ import java.util.Arrays;
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertTrue;
 import static org.briarproject.bramble.api.identity.Author.Status.UNKNOWN;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
@@ -38,30 +39,19 @@ import static org.mockito.Mockito.verify;
 		packageName = "org.briarproject.briar")
 public class ForumActivityTest {
 
-	private final static String AUTHOR_1 = "Author 1";
-	private final static String AUTHOR_2 = "Author 2";
-	private final static String AUTHOR_3 = "Author 3";
-	private final static String AUTHOR_4 = "Author 4";
-	private final static String AUTHOR_5 = "Author 5";
-	private final static String AUTHOR_6 = "Author 6";
-
-	private final static String[] AUTHORS = {
-			AUTHOR_1, AUTHOR_2, AUTHOR_3, AUTHOR_4, AUTHOR_5, AUTHOR_6
-	};
-
-	private final static MessageId[] AUTHOR_IDS = new MessageId[AUTHORS.length];
+	private final static MessageId[] MESSAGE_IDS = new MessageId[6];
 
 	static {
-		for (int i = 0; i < AUTHOR_IDS.length; i++)
-			AUTHOR_IDS[i] = new MessageId(TestUtils.getRandomId());
+		for (int i = 0; i < MESSAGE_IDS.length; i++)
+			MESSAGE_IDS[i] = new MessageId(getRandomId());
 	}
 
-	private final static MessageId[] PARENT_AUTHOR_IDS = {
+	private final static MessageId[] PARENT_IDS = {
 			null,
-			AUTHOR_IDS[0],
-			AUTHOR_IDS[1],
-			AUTHOR_IDS[2],
-			AUTHOR_IDS[0],
+			MESSAGE_IDS[0],
+			MESSAGE_IDS[1],
+			MESSAGE_IDS[2],
+			MESSAGE_IDS[0],
 			null
 	};
 
@@ -86,7 +76,7 @@ public class ForumActivityTest {
 	public void setUp() {
 		MockitoAnnotations.initMocks(this);
 		Intent intent = new Intent();
-		intent.putExtra("briar.GROUP_ID", TestUtils.getRandomId());
+		intent.putExtra("briar.GROUP_ID", getRandomId());
 		forumActivity = Robolectric.buildActivity(TestForumActivity.class,
 				intent).create().start().resume().get();
 	}
@@ -94,11 +84,10 @@ public class ForumActivityTest {
 	private ThreadItemList<ForumItem> getDummyData() {
 		ForumItem[] forumItems = new ForumItem[6];
 		for (int i = 0; i < forumItems.length; i++) {
-			AuthorId authorId = new AuthorId(TestUtils.getRandomId());
-			byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-			Author author = new Author(authorId, AUTHORS[i], publicKey);
-			forumItems[i] = new ForumItem(AUTHOR_IDS[i], PARENT_AUTHOR_IDS[i],
-					AUTHORS[i], System.currentTimeMillis(), author, UNKNOWN);
+			Author author = getAuthor();
+			String content = getRandomString(MAX_FORUM_POST_BODY_LENGTH);
+			forumItems[i] = new ForumItem(MESSAGE_IDS[i], PARENT_IDS[i],
+					content, System.currentTimeMillis(), author, UNKNOWN);
 			forumItems[i].setLevel(LEVELS[i]);
 		}
 		ThreadItemList<ForumItem> list = new ThreadItemListImpl<>();
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java
index 4c8ff25d10..2348ae9279 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/blog/BlogConstants.java
@@ -25,6 +25,7 @@ public interface BlogConstants {
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_TIME_RECEIVED = "timeReceived";
 	String KEY_AUTHOR_ID = "id";
+	String KEY_FORMAT_VERSION = "formatVersion";
 	String KEY_AUTHOR_NAME = "name";
 	String KEY_PUBLIC_KEY = "publicKey";
 	String KEY_AUTHOR = "author";
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java
index 0fddb3aa73..43d9856c02 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/feed/FeedConstants.java
@@ -18,6 +18,7 @@ public interface FeedConstants {
 	// group metadata keys
 	String KEY_FEEDS = "feeds";
 	String KEY_FEED_URL = "feedURL";
+	String KEY_FORMAT_VERSION = "formatVersion";
 	String KEY_BLOG_TITLE = "blogTitle";
 	String KEY_PUBLIC_KEY = "publicKey";
 	String KEY_PRIVATE_KEY = "privateKey";
diff --git a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumConstants.java
index cca6c913e1..88f6d36020 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/forum/ForumConstants.java
@@ -14,27 +14,20 @@ public interface ForumConstants {
 	 */
 	int FORUM_SALT_LENGTH = 32;
 
-	/**
-	 * The maximum length of a forum post's content type in UTF-8 bytes.
-	 */
-	int MAX_CONTENT_TYPE_LENGTH = 50;
-
 	/**
 	 * The maximum length of a forum post's body in bytes.
 	 */
 	int MAX_FORUM_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
-	/* Forum Sharing Constants */
-	String FORUM_NAME = "forumName";
-	String FORUM_SALT = "forumSalt";
-
-	// Database keys
+	// Metadata keys
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_PARENT = "parent";
+	String KEY_AUTHOR = "author";
 	String KEY_ID = "id";
+	String KEY_FORMAT_VERSION = "formatVersion";
 	String KEY_NAME = "name";
-	String KEY_PUBLIC_NAME = "publicKey";
-	String KEY_AUTHOR = "author";
+	String KEY_PUBLIC_KEY = "publicKey";
 	String KEY_LOCAL = "local";
+	String KEY_READ = "read";
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java
index 8cf9d024a2..b3f17d7b54 100644
--- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogFactoryImpl.java
@@ -14,8 +14,7 @@ import org.briarproject.briar.api.blog.BlogFactory;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 import static org.briarproject.briar.api.blog.BlogManager.CLIENT_ID;
 import static org.briarproject.briar.api.blog.BlogManager.CLIENT_VERSION;
 
@@ -48,11 +47,12 @@ class BlogFactoryImpl implements BlogFactory {
 
 	private Blog createBlog(Author a, boolean rssFeed) {
 		try {
-			BdfList blog = BdfList.of(
+			BdfList authorList = BdfList.of(
+					a.getFormatVersion(),
 					a.getName(),
-					a.getPublicKey(),
-					rssFeed
+					a.getPublicKey()
 			);
+			BdfList blog = BdfList.of(authorList, rssFeed);
 			byte[] descriptor = clientHelper.toByteArray(blog);
 			Group g = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION,
 					descriptor);
@@ -63,20 +63,15 @@ class BlogFactoryImpl implements BlogFactory {
 	}
 
 	@Override
-	public Blog parseBlog(Group group) throws FormatException {
-		byte[] descriptor = group.getDescriptor();
-		// Author name, public key, RSS feed
-		BdfList blog = clientHelper.toList(descriptor);
-		String name = blog.getString(0);
-		if (name.length() > MAX_AUTHOR_NAME_LENGTH)
-			throw new IllegalArgumentException();
-		byte[] publicKey = blog.getRaw(1);
-		if (publicKey.length > MAX_PUBLIC_KEY_LENGTH)
-			throw new IllegalArgumentException();
+	public Blog parseBlog(Group g) throws FormatException {
+		// Author, RSS feed
+		BdfList descriptor = clientHelper.toList(g.getDescriptor());
+		checkSize(descriptor, 2);
+		BdfList authorList = descriptor.getList(0);
+		boolean rssFeed = descriptor.getBoolean(1);
 
-		Author author = authorFactory.createAuthor(name, publicKey);
-		boolean rssFeed = blog.getBoolean(2);
-		return new Blog(group, author, rssFeed);
+		Author author = clientHelper.parseAndValidateAuthor(authorList);
+		return new Blog(g, author, rssFeed);
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
index b4ff952a5a..61565d2932 100644
--- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogManagerImpl.java
@@ -49,10 +49,12 @@ import javax.annotation.Nullable;
 import javax.inject.Inject;
 
 import static org.briarproject.bramble.api.contact.ContactManager.RemoveContactHook;
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_FORMAT_VERSION;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
@@ -573,11 +575,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		long timestamp = meta.getLong(KEY_TIMESTAMP);
 		long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp);
 
-		BdfDictionary d = meta.getDictionary(KEY_AUTHOR);
-		AuthorId authorId = new AuthorId(d.getRaw(KEY_AUTHOR_ID));
-		String name = d.getString(KEY_AUTHOR_NAME);
-		byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
-		Author author = new Author(authorId, name, publicKey);
+		BdfDictionary authorDict = meta.getDictionary(KEY_AUTHOR);
+		AuthorId authorId = new AuthorId(authorDict.getRaw(KEY_AUTHOR_ID));
+		int formatVersion = authorDict.getLong(KEY_FORMAT_VERSION).intValue();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
+		String name = authorDict.getString(KEY_AUTHOR_NAME);
+		byte[] publicKey = authorDict.getRaw(KEY_PUBLIC_KEY);
+		Author author = new Author(authorId, formatVersion, name, publicKey);
 		boolean isFeedPost = meta.getBoolean(KEY_RSS_FEED, false);
 		Status authorStatus;
 		if (isFeedPost) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java
index 84c5877c20..e78497e55c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/blog/BlogPostValidator.java
@@ -34,6 +34,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_FORMAT_VERSION;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
@@ -115,9 +116,8 @@ class BlogPostValidator extends BdfMessageValidator {
 		Blog b = blogFactory.parseBlog(g);
 		Author a = b.getAuthor();
 		try {
-			clientHelper
-					.verifySignature(SIGNING_LABEL_POST, sig, a.getPublicKey(),
-							signed);
+			clientHelper.verifySignature(SIGNING_LABEL_POST, sig,
+					a.getPublicKey(), signed);
 		} catch (GeneralSecurityException e) {
 			throw new InvalidMessageException(e);
 		}
@@ -156,10 +156,9 @@ class BlogPostValidator extends BdfMessageValidator {
 
 		// Signature
 		byte[] sig = body.getRaw(3);
-		checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
-		BdfList signed =
-				BdfList.of(g.getId(), m.getTimestamp(), comment, pOriginalId,
-						currentId);
+		checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
+		BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), comment,
+				pOriginalId, currentId);
 		Blog b = blogFactory.parseBlog(g);
 		Author a = b.getAuthor();
 		try {
@@ -289,6 +288,7 @@ class BlogPostValidator extends BdfMessageValidator {
 	static BdfDictionary authorToBdfDictionary(Author a) {
 		return BdfDictionary.of(
 				new BdfEntry(KEY_AUTHOR_ID, a.getId()),
+				new BdfEntry(KEY_FORMAT_VERSION, a.getFormatVersion()),
 				new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
 				new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
 		);
diff --git a/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java
index ec5d9c31b5..a57d78927f 100644
--- a/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/feed/FeedFactoryImpl.java
@@ -17,7 +17,7 @@ import org.briarproject.briar.api.feed.Feed;
 
 import javax.inject.Inject;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
+import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_NAME_LENGTH;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_BLOG_TITLE;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_ADDED;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_AUTHOR;
@@ -25,6 +25,7 @@ import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_DESC;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_LAST_ENTRY;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_UPDATED;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEED_URL;
+import static org.briarproject.briar.api.feed.FeedConstants.KEY_FORMAT_VERSION;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_PRIVATE_KEY;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_PUBLIC_KEY;
 
@@ -48,13 +49,12 @@ class FeedFactoryImpl implements FeedFactory {
 	public Feed createFeed(String url, SyndFeed syndFeed) {
 		String title = syndFeed.getTitle();
 		if (title == null) title = "RSS";
-		title = StringUtils.truncateUtf8(title, MAX_AUTHOR_NAME_LENGTH);
+		else title = StringUtils.truncateUtf8(title, MAX_BLOG_NAME_LENGTH);
 
 		KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
-		LocalAuthor localAuthor = authorFactory
-				.createLocalAuthor(title,
-						keyPair.getPublic().getEncoded(),
-						keyPair.getPrivate().getEncoded());
+		LocalAuthor localAuthor = authorFactory.createLocalAuthor(title,
+				keyPair.getPublic().getEncoded(),
+				keyPair.getPrivate().getEncoded());
 		Blog blog = blogFactory.createFeedBlog(localAuthor);
 		long added = clock.currentTimeMillis();
 
@@ -73,11 +73,12 @@ class FeedFactoryImpl implements FeedFactory {
 	public Feed createFeed(BdfDictionary d) throws FormatException {
 		String url = d.getString(KEY_FEED_URL);
 
+		int formatVersion = d.getLong(KEY_FORMAT_VERSION).intValue();
 		String blogTitle = d.getString(KEY_BLOG_TITLE);
 		byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
 		byte[] privateKey = d.getRaw(KEY_PRIVATE_KEY);
-		LocalAuthor localAuthor = authorFactory
-				.createLocalAuthor(blogTitle, publicKey, privateKey);
+		LocalAuthor localAuthor = authorFactory.createLocalAuthor(
+				formatVersion, blogTitle, publicKey, privateKey);
 		Blog blog = blogFactory.createFeedBlog(localAuthor);
 
 		String desc = d.getOptionalString(KEY_FEED_DESC);
@@ -92,13 +93,14 @@ class FeedFactoryImpl implements FeedFactory {
 
 	@Override
 	public BdfDictionary feedToBdfDictionary(Feed feed) {
+		LocalAuthor localAuthor = feed.getLocalAuthor();
 		BdfDictionary d = BdfDictionary.of(
 				new BdfEntry(KEY_FEED_URL, feed.getUrl()),
-				new BdfEntry(KEY_BLOG_TITLE, feed.getLocalAuthor().getName()),
-				new BdfEntry(KEY_PUBLIC_KEY,
-						feed.getLocalAuthor().getPublicKey()),
-				new BdfEntry(KEY_PRIVATE_KEY,
-						feed.getLocalAuthor().getPrivateKey()),
+				new BdfEntry(KEY_FORMAT_VERSION,
+						localAuthor.getFormatVersion()),
+				new BdfEntry(KEY_BLOG_TITLE, localAuthor.getName()),
+				new BdfEntry(KEY_PUBLIC_KEY, localAuthor.getPublicKey()),
+				new BdfEntry(KEY_PRIVATE_KEY, localAuthor.getPrivateKey()),
 				new BdfEntry(KEY_FEED_ADDED, feed.getAdded()),
 				new BdfEntry(KEY_FEED_UPDATED, feed.getUpdated()),
 				new BdfEntry(KEY_FEED_LAST_ENTRY, feed.getLastEntryTime())
diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java
index d540661ec6..4588db4c8e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumFactoryImpl.java
@@ -31,7 +31,6 @@ class ForumFactoryImpl implements ForumFactory {
 	@Inject
 	ForumFactoryImpl(GroupFactory groupFactory, ClientHelper clientHelper,
 			SecureRandom random) {
-
 		this.groupFactory = groupFactory;
 		this.clientHelper = clientHelper;
 		this.random = random;
diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java
index 23a7c24bf5..9c700d2e32 100644
--- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumManagerImpl.java
@@ -45,13 +45,15 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
 import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_FORMAT_VERSION;
 import static org.briarproject.briar.api.forum.ForumConstants.KEY_ID;
 import static org.briarproject.briar.api.forum.ForumConstants.KEY_LOCAL;
 import static org.briarproject.briar.api.forum.ForumConstants.KEY_NAME;
 import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
-import static org.briarproject.briar.api.forum.ForumConstants.KEY_PUBLIC_NAME;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_PUBLIC_KEY;
 import static org.briarproject.briar.api.forum.ForumConstants.KEY_TIMESTAMP;
 import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
 
@@ -149,8 +151,9 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 			Author a = p.getAuthor();
 			BdfDictionary authorMeta = new BdfDictionary();
 			authorMeta.put(KEY_ID, a.getId());
+			authorMeta.put(KEY_FORMAT_VERSION, a.getFormatVersion());
 			authorMeta.put(KEY_NAME, a.getName());
-			authorMeta.put(KEY_PUBLIC_NAME, a.getPublicKey());
+			authorMeta.put(KEY_PUBLIC_KEY, a.getPublicKey());
 			meta.put(KEY_AUTHOR, authorMeta);
 			meta.put(KEY_LOCAL, true);
 			meta.put(MSG_KEY_READ, true);
@@ -300,9 +303,11 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 			parentId = new MessageId(meta.getRaw(KEY_PARENT));
 		BdfDictionary authorDict = meta.getDictionary(KEY_AUTHOR);
 		AuthorId authorId = new AuthorId(authorDict.getRaw(KEY_ID));
+		int formatVersion = authorDict.getLong(KEY_FORMAT_VERSION).intValue();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
 		String name = authorDict.getString(KEY_NAME);
-		byte[] publicKey = authorDict.getRaw(KEY_PUBLIC_NAME);
-		Author author = new Author(authorId, name, publicKey);
+		byte[] publicKey = authorDict.getRaw(KEY_PUBLIC_KEY);
+		Author author = new Author(authorId, formatVersion, name, publicKey);
 		Status status = statuses.get(authorId);
 		if (status == null)
 			status = identityManager.getAuthorStatus(txn, author.getId());
diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java
index 8e742a7550..9050dc272a 100644
--- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumModule.java
@@ -2,16 +2,12 @@ package org.briarproject.briar.forum;
 
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.MetadataEncoder;
-import org.briarproject.bramble.api.identity.AuthorFactory;
-import org.briarproject.bramble.api.sync.GroupFactory;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.forum.ForumFactory;
 import org.briarproject.briar.api.forum.ForumManager;
 import org.briarproject.briar.api.forum.ForumPostFactory;
 
-import java.security.SecureRandom;
-
 import javax.inject.Inject;
 import javax.inject.Singleton;
 
@@ -34,10 +30,8 @@ public class ForumModule {
 	@Singleton
 	ForumManager provideForumManager(ForumManagerImpl forumManager,
 			ValidationManager validationManager) {
-
 		validationManager.registerIncomingMessageHook(CLIENT_ID,
 				forumManager);
-
 		return forumManager;
 	}
 
@@ -48,19 +42,17 @@ public class ForumModule {
 	}
 
 	@Provides
-	ForumFactory provideForumFactory(GroupFactory groupFactory,
-			ClientHelper clientHelper, SecureRandom random) {
-		return new ForumFactoryImpl(groupFactory, clientHelper, random);
+	ForumFactory provideForumFactory(ForumFactoryImpl forumFactory) {
+		return forumFactory;
 	}
 
 	@Provides
 	@Singleton
 	ForumPostValidator provideForumPostValidator(
-			ValidationManager validationManager, AuthorFactory authorFactory,
-			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock) {
-		ForumPostValidator validator = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
+			ValidationManager validationManager, ClientHelper clientHelper,
+			MetadataEncoder metadataEncoder, Clock clock) {
+		ForumPostValidator validator = new ForumPostValidator(clientHelper,
+				metadataEncoder, clock);
 		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostFactoryImpl.java
index 2c26fa3667..b07001755b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostFactoryImpl.java
@@ -39,8 +39,11 @@ class ForumPostFactoryImpl implements ForumPostFactory {
 		if (StringUtils.utf8IsTooLong(body, MAX_FORUM_POST_BODY_LENGTH))
 			throw new IllegalArgumentException();
 		// Serialise the data to be signed
-		BdfList authorList =
-				BdfList.of(author.getName(), author.getPublicKey());
+		BdfList authorList = BdfList.of(
+				author.getFormatVersion(),
+				author.getName(),
+				author.getPublicKey()
+		);
 		BdfList signed = BdfList.of(groupId, timestamp, parent, authorList,
 				body);
 		// Sign the data
diff --git a/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostValidator.java b/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostValidator.java
index 527960530a..79899ce66e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/forum/ForumPostValidator.java
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.InvalidMessageException;
@@ -23,11 +22,17 @@ import java.util.Collections;
 
 import javax.annotation.concurrent.Immutable;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_FORMAT_VERSION;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_ID;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_NAME;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_PUBLIC_KEY;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_READ;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_TIMESTAMP;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.briarproject.briar.api.forum.ForumPostFactory.SIGNING_LABEL_POST;
 
@@ -35,12 +40,9 @@ import static org.briarproject.briar.api.forum.ForumPostFactory.SIGNING_LABEL_PO
 @NotNullByDefault
 class ForumPostValidator extends BdfMessageValidator {
 
-	private final AuthorFactory authorFactory;
-
-	ForumPostValidator(AuthorFactory authorFactory, ClientHelper clientHelper,
+	ForumPostValidator(ClientHelper clientHelper,
 			MetadataEncoder metadataEncoder, Clock clock) {
 		super(clientHelper, metadataEncoder, clock);
-		this.authorFactory = authorFactory;
 	}
 
 	@Override
@@ -55,24 +57,18 @@ class ForumPostValidator extends BdfMessageValidator {
 
 		// Author
 		BdfList authorList = body.getList(1);
-		// Name, public key
-		checkSize(authorList, 2);
-		String name = authorList.getString(0);
-		checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey = authorList.getRaw(1);
-		checkLength(publicKey, 0, MAX_PUBLIC_KEY_LENGTH);
-		Author author = authorFactory.createAuthor(name, publicKey);
+		Author author = clientHelper.parseAndValidateAuthor(authorList);
 
 		// Forum post body
-		String forumPostBody = body.getString(2);
-		checkLength(forumPostBody, 0, MAX_FORUM_POST_BODY_LENGTH);
+		String content = body.getString(2);
+		checkLength(content, 0, MAX_FORUM_POST_BODY_LENGTH);
 
 		// Signature
 		byte[] sig = body.getRaw(3);
-		checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
+		checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
 		// Verify the signature
 		BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), parent,
-				authorList, forumPostBody);
+				authorList, content);
 		try {
 			clientHelper.verifySignature(SIGNING_LABEL_POST, sig,
 					author.getPublicKey(), signed);
@@ -83,17 +79,18 @@ class ForumPostValidator extends BdfMessageValidator {
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
 		Collection<MessageId> dependencies = Collections.emptyList();
-		meta.put("timestamp", m.getTimestamp());
+		meta.put(KEY_TIMESTAMP, m.getTimestamp());
 		if (parent != null) {
-			meta.put("parent", parent);
+			meta.put(KEY_PARENT, parent);
 			dependencies = Collections.singletonList(new MessageId(parent));
 		}
 		BdfDictionary authorMeta = new BdfDictionary();
-		authorMeta.put("id", author.getId());
-		authorMeta.put("name", author.getName());
-		authorMeta.put("publicKey", author.getPublicKey());
-		meta.put("author", authorMeta);
-		meta.put("read", false);
+		authorMeta.put(KEY_ID, author.getId());
+		authorMeta.put(KEY_FORMAT_VERSION, author.getFormatVersion());
+		authorMeta.put(KEY_NAME, author.getName());
+		authorMeta.put(KEY_PUBLIC_KEY, author.getPublicKey());
+		meta.put(KEY_AUTHOR, authorMeta);
+		meta.put(KEY_READ, false);
 		return new BdfMessageContext(meta, dependencies);
 	}
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java
index 1bf8ea37a8..82b6cc9499 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeManager.java
@@ -169,6 +169,7 @@ class IntroduceeManager {
 		d.put(ANSWERED, false);
 
 		// check if the contact we are introduced to does already exist
+		// TODO: Exchange author format version
 		AuthorId remoteAuthorId = authorFactory
 				.createAuthor(message.getString(NAME),
 						message.getRaw(PUBLIC_KEY)).getId();
@@ -339,6 +340,7 @@ class IntroduceeManager {
 			long timestamp = Math.min(ourTime, theirTime);
 
 			// Add the contact to the database as inactive
+			// TODO: Exchange author format version
 			Author remoteAuthor = authorFactory
 					.createAuthor(localState.getString(NAME),
 							localState.getRaw(PUBLIC_KEY));
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java
index 3b71d42d58..705a36f00a 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java
@@ -87,6 +87,8 @@ class IntroductionValidator extends BdfQueueMessageValidator {
 
 		checkSize(message, 4, 5);
 
+		// TODO: Exchange author format version
+
 		// parse contact name
 		String name = message.getString(2);
 		checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
@@ -131,7 +133,7 @@ class IntroductionValidator extends BdfQueueMessageValidator {
 
 			// parse ephemeral public key
 			pubkey = message.getRaw(4);
-			checkLength(pubkey, 0, MAX_PUBLIC_KEY_LENGTH);
+			checkLength(pubkey, 1, MAX_PUBLIC_KEY_LENGTH);
 
 			// parse transport properties
 			tp = message.getDictionary(5);
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupConstants.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupConstants.java
index 684ecb401e..126f908511 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupConstants.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupConstants.java
@@ -4,13 +4,14 @@ import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ
 
 interface GroupConstants {
 
-	// Database keys
+	// Metadata keys
 	String KEY_TYPE = "type";
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_READ = MSG_KEY_READ;
 	String KEY_PARENT_MSG_ID = "parentMsgId";
 	String KEY_PREVIOUS_MSG_ID = "previousMsgId";
 	String KEY_MEMBER_ID = "memberId";
+	String KEY_MEMBER_FORMAT_VERSION = "formatVersion";
 	String KEY_MEMBER_NAME = "memberName";
 	String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey";
 	String KEY_INITIAL_JOIN_MSG = "initialJoinMsg";
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageFactoryImpl.java
index 7f0404872a..318acac0f2 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageFactoryImpl.java
@@ -50,47 +50,73 @@ class GroupMessageFactoryImpl implements GroupMessageFactory {
 			LocalAuthor member, @Nullable BdfList invite) {
 		try {
 			// Generate the signature
-			int type = JOIN.getInt();
-			BdfList toSign = BdfList.of(groupId, timestamp, type,
-					member.getName(), member.getPublicKey(), invite);
-			byte[] memberSignature = clientHelper
-					.sign(SIGNING_LABEL_JOIN, toSign, member.getPrivateKey());
+			BdfList memberList = BdfList.of(
+					member.getFormatVersion(),
+					member.getName(),
+					member.getPublicKey()
+			);
+			BdfList toSign = BdfList.of(
+					groupId,
+					timestamp,
+					memberList,
+					invite
+			);
+			byte[] memberSignature = clientHelper.sign(SIGNING_LABEL_JOIN,
+					toSign, member.getPrivateKey());
 
 			// Compose the message
-			BdfList body =
-					BdfList.of(type, member.getName(),
-							member.getPublicKey(), invite, memberSignature);
+			BdfList body = BdfList.of(
+					JOIN.getInt(),
+					memberList,
+					invite,
+					memberSignature
+			);
 			Message m = clientHelper.createMessage(groupId, timestamp, body);
-
 			return new GroupMessage(m, null, member);
-		} catch (GeneralSecurityException | FormatException e) {
-			throw new RuntimeException(e);
+		} catch (GeneralSecurityException e) {
+			throw new IllegalArgumentException(e);
+		} catch (FormatException e) {
+			throw new AssertionError(e);
 		}
 	}
 
 	@Override
 	public GroupMessage createGroupMessage(GroupId groupId, long timestamp,
-			@Nullable MessageId parentId, LocalAuthor author, String content,
+			@Nullable MessageId parentId, LocalAuthor member, String content,
 			MessageId previousMsgId) {
 		try {
 			// Generate the signature
-			int type = POST.getInt();
-			BdfList toSign = BdfList.of(groupId, timestamp, type,
-					author.getName(), author.getPublicKey(), parentId,
-					previousMsgId, content);
-			byte[] signature = clientHelper
-					.sign(SIGNING_LABEL_POST, toSign, author.getPrivateKey());
+			BdfList memberList = BdfList.of(
+					member.getFormatVersion(),
+					member.getName(),
+					member.getPublicKey()
+			);
+			BdfList toSign = BdfList.of(
+					groupId,
+					timestamp,
+					memberList,
+					parentId,
+					previousMsgId,
+					content
+			);
+			byte[] signature = clientHelper.sign(SIGNING_LABEL_POST, toSign,
+					member.getPrivateKey());
 
 			// Compose the message
-			BdfList body =
-					BdfList.of(type, author.getName(),
-							author.getPublicKey(), parentId, previousMsgId,
-							content, signature);
+			BdfList body = BdfList.of(
+					POST.getInt(),
+					memberList,
+					parentId,
+					previousMsgId,
+					content,
+					signature
+			);
 			Message m = clientHelper.createMessage(groupId, timestamp, body);
-
-			return new GroupMessage(m, parentId, author);
-		} catch (GeneralSecurityException | FormatException e) {
-			throw new RuntimeException(e);
+			return new GroupMessage(m, parentId, member);
+		} catch (GeneralSecurityException e) {
+			throw new IllegalArgumentException(e);
+		} catch (FormatException e) {
+			throw new AssertionError(e);
 		}
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageValidator.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageValidator.java
index 872da6fb96..ad2be2195b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/GroupMessageValidator.java
@@ -8,7 +8,6 @@ import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.InvalidMessageException;
@@ -25,8 +24,6 @@ import java.util.Collection;
 
 import javax.annotation.concurrent.Immutable;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -37,6 +34,7 @@ import static org.briarproject.briar.api.privategroup.MessageType.POST;
 import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
 import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
+import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_FORMAT_VERSION;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_ID;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_NAME;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
@@ -51,16 +49,13 @@ import static org.briarproject.briar.privategroup.GroupConstants.KEY_TYPE;
 class GroupMessageValidator extends BdfMessageValidator {
 
 	private final PrivateGroupFactory privateGroupFactory;
-	private final AuthorFactory authorFactory;
 	private final GroupInvitationFactory groupInvitationFactory;
 
 	GroupMessageValidator(PrivateGroupFactory privateGroupFactory,
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock, AuthorFactory authorFactory,
-			GroupInvitationFactory groupInvitationFactory) {
+			Clock clock, GroupInvitationFactory groupInvitationFactory) {
 		super(clientHelper, metadataEncoder, clock);
 		this.privateGroupFactory = privateGroupFactory;
-		this.authorFactory = authorFactory;
 		this.groupInvitationFactory = groupInvitationFactory;
 	}
 
@@ -68,20 +63,15 @@ class GroupMessageValidator extends BdfMessageValidator {
 	protected BdfMessageContext validateMessage(Message m, Group g,
 			BdfList body) throws InvalidMessageException, FormatException {
 
-		checkSize(body, 5, 7);
+		checkSize(body, 4, 6);
 
-		// message type (int)
+		// Message type (int)
 		int type = body.getLong(0).intValue();
 
-		// member_name (string)
-		String memberName = body.getString(1);
-		checkLength(memberName, 1, MAX_AUTHOR_NAME_LENGTH);
+		// Member (list of int, string, raw)
+		BdfList memberList = body.getList(1);
+		Author member = clientHelper.parseAndValidateAuthor(memberList);
 
-		// member_public_key (raw)
-		byte[] memberPublicKey = body.getRaw(2);
-		checkLength(memberPublicKey, 1, MAX_PUBLIC_KEY_LENGTH);
-
-		Author member = authorFactory.createAuthor(memberName, memberPublicKey);
 		BdfMessageContext c;
 		if (type == JOIN.getInt()) {
 			c = validateJoin(m, g, body, member);
@@ -97,64 +87,54 @@ class GroupMessageValidator extends BdfMessageValidator {
 	}
 
 	private BdfMessageContext validateJoin(Message m, Group g, BdfList body,
-			Author member)
-			throws InvalidMessageException, FormatException {
+			Author member) throws FormatException {
+		// Message type, member, optional invite, member's signature
+		checkSize(body, 4);
+		BdfList inviteList = body.getOptionalList(2);
+		byte[] memberSignature = body.getRaw(3);
+		checkLength(memberSignature, 1, MAX_SIGNATURE_LENGTH);
 
-		// The content is a BDF list with five elements
-		checkSize(body, 5);
+		// Invite is null if the member is the creator of the private group
 		PrivateGroup pg = privateGroupFactory.parsePrivateGroup(g);
-
-		// invite is null if the member is the creator of the private group
 		Author creator = pg.getCreator();
-		boolean isCreator = false;
-		BdfList invite = body.getOptionalList(3);
-		if (invite == null) {
-			if (!member.equals(creator))
-				throw new InvalidMessageException();
-			isCreator = true;
+		boolean isCreator = member.equals(creator);
+		if (isCreator) {
+			if (inviteList != null) throw new FormatException();
 		} else {
-			if (member.equals(creator))
-				throw new InvalidMessageException();
-
-			// Otherwise invite is a list with two elements
-			checkSize(invite, 2);
-
-			// invite_timestamp (int)
-			// join_timestamp must be greater than invite_timestamp
-			long inviteTimestamp = invite.getLong(0);
+			if (inviteList == null) throw new FormatException();
+			// Timestamp, creator's signature
+			checkSize(inviteList, 2);
+			// Join timestamp must be greater than invite timestamp
+			long inviteTimestamp = inviteList.getLong(0);
 			if (m.getTimestamp() <= inviteTimestamp)
-				throw new InvalidMessageException();
-
-			// creator_signature (raw)
-			byte[] creatorSignature = invite.getRaw(1);
+				throw new FormatException();
+			byte[] creatorSignature = inviteList.getRaw(1);
 			checkLength(creatorSignature, 1, MAX_SIGNATURE_LENGTH);
-
-			// the invite token is signed by the creator of the private group
-			BdfList token = groupInvitationFactory
-					.createInviteToken(creator.getId(), member.getId(),
-							pg.getId(), inviteTimestamp);
+			// The invite token is signed by the creator of the private group
+			BdfList token = groupInvitationFactory.createInviteToken(
+					creator.getId(), member.getId(), g.getId(),
+					inviteTimestamp);
 			try {
-				clientHelper
-						.verifySignature(SIGNING_LABEL_INVITE, creatorSignature,
-								creator.getPublicKey(), token);
+				clientHelper.verifySignature(SIGNING_LABEL_INVITE,
+						creatorSignature, creator.getPublicKey(), token);
 			} catch (GeneralSecurityException e) {
-				throw new InvalidMessageException(e);
+				throw new FormatException();
 			}
 		}
 
-		// member_signature (raw)
-		// a signature with the member's private key over a list with 6 elements
-		byte[] memberSignature = body.getRaw(4);
-		checkLength(memberSignature, 1, MAX_SIGNATURE_LENGTH);
-
-		// Verify Signature
-		BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), JOIN.getInt(),
-				member.getName(), member.getPublicKey(), invite);
+		// Verify the member's signature
+		BdfList memberList = body.getList(1); // Already validated
+		BdfList signed = BdfList.of(
+				g.getId(),
+				m.getTimestamp(),
+				memberList,
+				inviteList
+		);
 		try {
 			clientHelper.verifySignature(SIGNING_LABEL_JOIN, memberSignature,
 					member.getPublicKey(), signed);
 		} catch (GeneralSecurityException e) {
-			throw new InvalidMessageException(e);
+			throw new FormatException();
 		}
 
 		// Return the metadata and no dependencies
@@ -164,44 +144,38 @@ class GroupMessageValidator extends BdfMessageValidator {
 	}
 
 	private BdfMessageContext validatePost(Message m, Group g, BdfList body,
-			Author member)
-			throws InvalidMessageException, FormatException {
-
-		// The content is a BDF list with seven elements
-		checkSize(body, 7);
-
-		// parent_id (raw or null)
-		// the identifier of the post to which this is a reply, if any
-		byte[] parentId = body.getOptionalRaw(3);
+			Author member) throws FormatException {
+		// Message type, member, optional parent ID, previous message ID,
+		// content, signature
+		checkSize(body, 6);
+		byte[] parentId = body.getOptionalRaw(2);
 		checkLength(parentId, MessageId.LENGTH);
-
-		// previous_message_id (raw)
-		// the identifier of the member's previous post or join message
-		byte[] previousMessageId = body.getRaw(4);
+		byte[] previousMessageId = body.getRaw(3);
 		checkLength(previousMessageId, MessageId.LENGTH);
-
-		// content (string)
-		String content = body.getString(5);
+		String content = body.getString(4);
 		checkLength(content, 1, MAX_GROUP_POST_BODY_LENGTH);
-
-		// signature (raw)
-		// a signature with the member's private key over a list with 7 elements
-		byte[] signature = body.getRaw(6);
+		byte[] signature = body.getRaw(5);
 		checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
 
-		// Verify Signature
-		BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), POST.getInt(),
-				member.getName(), member.getPublicKey(), parentId,
-				previousMessageId, content);
+		// Verify the member's signature
+		BdfList memberList = body.getList(1); // Already validated
+		BdfList signed = BdfList.of(
+				g.getId(),
+				m.getTimestamp(),
+				memberList,
+				parentId,
+				previousMessageId,
+				content
+		);
 		try {
 			clientHelper.verifySignature(SIGNING_LABEL_POST, signature,
 					member.getPublicKey(), signed);
 		} catch (GeneralSecurityException e) {
-			throw new InvalidMessageException(e);
+			throw new FormatException();
 		}
 
-		// The parent post, if any,
-		// and the member's previous message are dependencies
+		// The parent post, if any, and the member's previous message are
+		// dependencies
 		Collection<MessageId> dependencies = new ArrayList<>();
 		if (parentId != null) dependencies.add(new MessageId(parentId));
 		dependencies.add(new MessageId(previousMessageId));
@@ -214,10 +188,12 @@ class GroupMessageValidator extends BdfMessageValidator {
 	}
 
 	private void addMessageMetadata(BdfMessageContext c, Author member,
-			long time) {
-		c.getDictionary().put(KEY_TIMESTAMP, time);
+			long timestamp) {
+		c.getDictionary().put(KEY_TIMESTAMP, timestamp);
 		c.getDictionary().put(KEY_READ, false);
 		c.getDictionary().put(KEY_MEMBER_ID, member.getId());
+		c.getDictionary().put(KEY_MEMBER_FORMAT_VERSION,
+				member.getFormatVersion());
 		c.getDictionary().put(KEY_MEMBER_NAME, member.getName());
 		c.getDictionary().put(KEY_MEMBER_PUBLIC_KEY, member.getPublicKey());
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java
index bcf7846d62..dc5d2f3930 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupFactoryImpl.java
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupFactory;
@@ -17,6 +16,8 @@ import java.security.SecureRandom;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.util.ValidationUtils.checkLength;
+import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
 import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
 import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
@@ -28,27 +29,22 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 
 	private final GroupFactory groupFactory;
 	private final ClientHelper clientHelper;
-	private final AuthorFactory authorFactory;
 	private final SecureRandom random;
 
 	@Inject
 	PrivateGroupFactoryImpl(GroupFactory groupFactory,
-			ClientHelper clientHelper, AuthorFactory authorFactory,
-			SecureRandom random) {
+			ClientHelper clientHelper, SecureRandom random) {
 
 		this.groupFactory = groupFactory;
 		this.clientHelper = clientHelper;
-		this.authorFactory = authorFactory;
 		this.random = random;
 	}
 
 	@Override
 	public PrivateGroup createPrivateGroup(String name, Author author) {
 		int length = StringUtils.toUtf8(name).length;
-		if (length == 0) throw new IllegalArgumentException("Group name empty");
-		if (length > MAX_GROUP_NAME_LENGTH)
-			throw new IllegalArgumentException(
-					"Group name exceeds maximum length");
+		if (length == 0 || length > MAX_GROUP_NAME_LENGTH)
+			throw new IllegalArgumentException();
 		byte[] salt = new byte[GROUP_SALT_LENGTH];
 		random.nextBytes(salt);
 		return createPrivateGroup(name, author, salt);
@@ -58,12 +54,12 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 	public PrivateGroup createPrivateGroup(String name, Author author,
 			byte[] salt) {
 		try {
-			BdfList group = BdfList.of(
-					name,
+			BdfList creatorList = BdfList.of(
+					author.getFormatVersion(),
 					author.getName(),
-					author.getPublicKey(),
-					salt
+					author.getPublicKey()
 			);
+			BdfList group = BdfList.of(creatorList, name, salt);
 			byte[] descriptor = clientHelper.toByteArray(group);
 			Group g = groupFactory.createGroup(CLIENT_ID, CLIENT_VERSION,
 					descriptor);
@@ -74,12 +70,18 @@ class PrivateGroupFactoryImpl implements PrivateGroupFactory {
 	}
 
 	@Override
-	public PrivateGroup parsePrivateGroup(Group group) throws FormatException {
-		byte[] descriptor = group.getDescriptor();
-		BdfList list = clientHelper.toList(descriptor);
-		Author a =
-				authorFactory.createAuthor(list.getString(1), list.getRaw(2));
-		return new PrivateGroup(group, list.getString(0), a, list.getRaw(3));
+	public PrivateGroup parsePrivateGroup(Group g) throws FormatException {
+		// Creator, group name, salt
+		BdfList descriptor = clientHelper.toList(g.getDescriptor());
+		checkSize(descriptor, 3);
+		BdfList creatorList = descriptor.getList(0);
+		String groupName = descriptor.getString(1);
+		checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH);
+		byte[] salt = descriptor.getRaw(2);
+		checkLength(salt, GROUP_SALT_LENGTH);
+
+		Author creator = clientHelper.parseAndValidateAuthor(creatorList);
+		return new PrivateGroup(g, groupName, creator, salt);
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java
index 28c547a5da..67919a2630 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupManagerImpl.java
@@ -54,6 +54,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import javax.annotation.concurrent.ThreadSafe;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
 import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.bramble.api.identity.Author.Status.UNVERIFIED;
 import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
@@ -69,6 +70,7 @@ import static org.briarproject.briar.privategroup.GroupConstants.GROUP_KEY_MEMBE
 import static org.briarproject.briar.privategroup.GroupConstants.GROUP_KEY_OUR_GROUP;
 import static org.briarproject.briar.privategroup.GroupConstants.GROUP_KEY_VISIBILITY;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
+import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_FORMAT_VERSION;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_ID;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_NAME;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
@@ -139,7 +141,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
 		BdfDictionary meta = new BdfDictionary();
 		meta.put(KEY_TYPE, JOIN.getInt());
 		meta.put(KEY_INITIAL_JOIN_MSG, creator);
-		addMessageMetadata(meta, m, true);
+		addMessageMetadata(meta, m);
 		clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 		messageTracker.trackOutgoingMessage(txn, m.getMessage());
 		addMember(txn, m.getMessage().getGroupId(), m.getMember(), VISIBLE);
@@ -217,7 +219,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
 			meta.put(KEY_TYPE, POST.getInt());
 			if (m.getParent() != null)
 				meta.put(KEY_PARENT_MSG_ID, m.getParent());
-			addMessageMetadata(meta, m, true);
+			addMessageMetadata(meta, m);
 			GroupId g = m.getMessage().getGroupId();
 			clientHelper.addLocalMessage(txn, m.getMessage(), meta, true);
 
@@ -239,11 +241,11 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
 				m.getMessage().getTimestamp(), m.getMember(), OURSELVES, true);
 	}
 
-	private void addMessageMetadata(BdfDictionary meta, GroupMessage m,
-			boolean read) {
+	private void addMessageMetadata(BdfDictionary meta, GroupMessage m) {
 		meta.put(KEY_TIMESTAMP, m.getMessage().getTimestamp());
-		meta.put(KEY_READ, read);
+		meta.put(KEY_READ, true);
 		meta.put(KEY_MEMBER_ID, m.getMember().getId());
+		meta.put(KEY_MEMBER_FORMAT_VERSION, m.getMember().getFormatVersion());
 		meta.put(KEY_MEMBER_NAME, m.getMember().getName());
 		meta.put(KEY_MEMBER_PUBLIC_KEY, m.getMember().getPublicKey());
 	}
@@ -316,9 +318,9 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
 	}
 
 	private String getMessageBody(BdfList body) throws FormatException {
-			// type(0), member_name(1), member_public_key(2), parent_id(3),
-			// previous_message_id(4), content(5), signature(6)
-			return body.getString(5);
+		// Message type (0), member (1), parent ID (2), previous message ID (3),
+		// content (4), signature (5)
+		return body.getString(4);
 	}
 
 	@Override
@@ -604,6 +606,7 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
 		BdfList members = meta.getList(GROUP_KEY_MEMBERS);
 		members.add(BdfDictionary.of(
 				new BdfEntry(KEY_MEMBER_ID, a.getId()),
+				new BdfEntry(KEY_MEMBER_FORMAT_VERSION, a.getFormatVersion()),
 				new BdfEntry(KEY_MEMBER_NAME, a.getName()),
 				new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()),
 				new BdfEntry(GROUP_KEY_VISIBILITY, v.getInt())
@@ -616,9 +619,11 @@ class PrivateGroupManagerImpl extends BdfIncomingMessageHook
 
 	private Author getAuthor(BdfDictionary meta) throws FormatException {
 		AuthorId authorId = new AuthorId(meta.getRaw(KEY_MEMBER_ID));
+		int formatVersion = meta.getLong(KEY_MEMBER_FORMAT_VERSION).intValue();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
 		String name = meta.getString(KEY_MEMBER_NAME);
 		byte[] publicKey = meta.getRaw(KEY_MEMBER_PUBLIC_KEY);
-		return new Author(authorId, name, publicKey);
+		return new Author(authorId, formatVersion, name, publicKey);
 	}
 
 	private Visibility getVisibility(BdfDictionary meta)
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java
index 883664e036..8b2bb3b66e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/PrivateGroupModule.java
@@ -2,7 +2,6 @@ package org.briarproject.briar.privategroup;
 
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.MetadataEncoder;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.briar.api.privategroup.GroupMessageFactory;
@@ -54,12 +53,11 @@ public class PrivateGroupModule {
 	GroupMessageValidator provideGroupMessageValidator(
 			PrivateGroupFactory privateGroupFactory,
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock, AuthorFactory authorFactory,
-			GroupInvitationFactory groupInvitationFactory,
+			Clock clock, GroupInvitationFactory groupInvitationFactory,
 			ValidationManager validationManager) {
 		GroupMessageValidator validator = new GroupMessageValidator(
 				privateGroupFactory, clientHelper, metadataEncoder, clock,
-				authorFactory, groupInvitationFactory);
+				groupInvitationFactory);
 		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java
index cc1049683b..c68f7b6625 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngine.java
@@ -140,10 +140,10 @@ abstract class AbstractProtocolEngine<S extends Session>
 		return m;
 	}
 
-	void markMessageVisibleInUi(Transaction txn, MessageId m, boolean visible)
+	void markMessageVisibleInUi(Transaction txn, MessageId m)
 			throws DbException {
 		BdfDictionary meta = new BdfDictionary();
-		messageEncoder.setVisibleInUi(meta, visible);
+		messageEncoder.setVisibleInUi(meta, true);
 		try {
 			clientHelper.mergeMessageMetadata(txn, m, meta);
 		} catch (FormatException e) {
@@ -174,10 +174,9 @@ abstract class AbstractProtocolEngine<S extends Session>
 			markMessageAvailableToAnswer(txn, m, false);
 	}
 
-	void markInviteAccepted(Transaction txn, MessageId m, boolean accepted)
-			throws DbException {
+	void markInviteAccepted(Transaction txn, MessageId m) throws DbException {
 		BdfDictionary meta = new BdfDictionary();
-		messageEncoder.setInvitationAccepted(meta, accepted);
+		messageEncoder.setInvitationAccepted(meta, true);
 		try {
 			clientHelper.mergeMessageMetadata(txn, m, meta);
 		} catch (FormatException e) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java
index ad550742ff..ec5315a6d0 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/CreatorProtocolEngine.java
@@ -183,7 +183,7 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
 		// Send a JOIN message
 		Message sent = sendJoinMessage(txn, s, false);
 		// Mark the response visible in the UI
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
 				m.getTimestamp(), false);
@@ -207,7 +207,7 @@ class CreatorProtocolEngine extends AbstractProtocolEngine<CreatorSession> {
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abort(txn, s);
 		// Mark the response visible in the UI
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
 				m.getTimestamp(), false);
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java
index 1c2ecc00ef..ba921f6129 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationModule.java
@@ -3,7 +3,6 @@ package org.briarproject.briar.privategroup.invitation;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.contact.ContactManager;
 import org.briarproject.bramble.api.data.MetadataEncoder;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.bramble.api.system.Clock;
@@ -53,13 +52,12 @@ public class GroupInvitationModule {
 	@Singleton
 	GroupInvitationValidator provideGroupInvitationValidator(
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock, AuthorFactory authorFactory,
-			PrivateGroupFactory privateGroupFactory,
+			Clock clock, PrivateGroupFactory privateGroupFactory,
 			MessageEncoder messageEncoder,
 			ValidationManager validationManager) {
 		GroupInvitationValidator validator = new GroupInvitationValidator(
-				clientHelper, metadataEncoder, clock, authorFactory,
-				privateGroupFactory, messageEncoder);
+				clientHelper, metadataEncoder, clock, privateGroupFactory,
+				messageEncoder);
 		validationManager.registerMessageValidator(CLIENT_ID, validator);
 		return validator;
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidator.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidator.java
index 1b111f5dc9..8d7f0feb34 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidator.java
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupId;
@@ -23,10 +22,7 @@ import java.security.GeneralSecurityException;
 import java.util.Collections;
 
 import javax.annotation.concurrent.Immutable;
-import javax.inject.Inject;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
@@ -43,18 +39,14 @@ import static org.briarproject.briar.privategroup.invitation.MessageType.LEAVE;
 @NotNullByDefault
 class GroupInvitationValidator extends BdfMessageValidator {
 
-	private final AuthorFactory authorFactory;
 	private final PrivateGroupFactory privateGroupFactory;
 	private final MessageEncoder messageEncoder;
 
-	@Inject
 	GroupInvitationValidator(ClientHelper clientHelper,
 			MetadataEncoder metadataEncoder, Clock clock,
-			AuthorFactory authorFactory,
 			PrivateGroupFactory privateGroupFactory,
 			MessageEncoder messageEncoder) {
 		super(clientHelper, metadataEncoder, clock);
-		this.authorFactory = authorFactory;
 		this.privateGroupFactory = privateGroupFactory;
 		this.messageEncoder = messageEncoder;
 	}
@@ -79,22 +71,20 @@ class GroupInvitationValidator extends BdfMessageValidator {
 
 	private BdfMessageContext validateInviteMessage(Message m, BdfList body)
 			throws FormatException {
-		checkSize(body, 7);
-		String groupName = body.getString(1);
+		// Message type, creator, group name, salt, optional message, signature
+		checkSize(body, 6);
+		BdfList creatorList = body.getList(1);
+		String groupName = body.getString(2);
 		checkLength(groupName, 1, MAX_GROUP_NAME_LENGTH);
-		String creatorName = body.getString(2);
-		checkLength(creatorName, 1, MAX_AUTHOR_NAME_LENGTH);
-		byte[] creatorPublicKey = body.getRaw(3);
-		checkLength(creatorPublicKey, 1, MAX_PUBLIC_KEY_LENGTH);
-		byte[] salt = body.getRaw(4);
+		byte[] salt = body.getRaw(3);
 		checkLength(salt, GROUP_SALT_LENGTH);
-		String message = body.getOptionalString(5);
+		String message = body.getOptionalString(4);
 		checkLength(message, 1, MAX_GROUP_INVITATION_MSG_LENGTH);
-		byte[] signature = body.getRaw(6);
+		byte[] signature = body.getRaw(5);
 		checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
-		// Create the private group
-		Author creator = authorFactory.createAuthor(creatorName,
-				creatorPublicKey);
+
+		// Validate the creator and create the private group
+		Author creator = clientHelper.parseAndValidateAuthor(creatorList);
 		PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
 				groupName, creator, salt);
 		// Verify the signature
@@ -105,7 +95,7 @@ class GroupInvitationValidator extends BdfMessageValidator {
 		);
 		try {
 			clientHelper.verifySignature(SIGNING_LABEL_INVITE, signature,
-					creatorPublicKey, signed);
+					creator.getPublicKey(), signed);
 		} catch (GeneralSecurityException e) {
 			throw new FormatException();
 		}
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java
index f4782a3875..f181d1f776 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngine.java
@@ -173,7 +173,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
 		if (inviteId == null) throw new IllegalStateException();
 		markMessageAvailableToAnswer(txn, inviteId, false);
 		// Record the response
-		markInviteAccepted(txn, inviteId, true);
+		markInviteAccepted(txn, inviteId);
 		// Send a JOIN message
 		Message sent = sendJoinMessage(txn, s, true);
 		// Track the message
@@ -228,7 +228,7 @@ class InviteeProtocolEngine extends AbstractProtocolEngine<InviteeSession> {
 		if (!contact.getId().equals(m.getCreator().getId()))
 			return abort(txn, s);
 		// Mark the invite message visible in the UI and available to answer
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		markMessageAvailableToAnswer(txn, m.getId(), true);
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageEncoderImpl.java
index a9ff4ac9e3..cb77567f23 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageEncoderImpl.java
@@ -78,11 +78,15 @@ class MessageEncoderImpl implements MessageEncoder {
 			GroupId privateGroupId, long timestamp, String groupName,
 			Author creator, byte[] salt, @Nullable String message,
 			byte[] signature) {
+		BdfList creatorList = BdfList.of(
+				creator.getFormatVersion(),
+				creator.getName(),
+				creator.getPublicKey()
+		);
 		BdfList body = BdfList.of(
 				INVITE.getValue(),
+				creatorList,
 				groupName,
-				creator.getName(),
-				creator.getPublicKey(),
 				salt,
 				message,
 				signature
diff --git a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java
index d5160b0d84..5c1ad6b7ad 100644
--- a/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/privategroup/invitation/MessageParserImpl.java
@@ -19,6 +19,7 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
 import static org.briarproject.briar.client.MessageTrackerConstants.MSG_KEY_READ;
 import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_AVAILABLE_TO_ANSWER;
 import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.MSG_KEY_INVITATION_ACCEPTED;
@@ -39,7 +40,8 @@ class MessageParserImpl implements MessageParser {
 
 	@Inject
 	MessageParserImpl(AuthorFactory authorFactory,
-			PrivateGroupFactory privateGroupFactory, ClientHelper clientHelper) {
+			PrivateGroupFactory privateGroupFactory,
+			ClientHelper clientHelper) {
 		this.authorFactory = authorFactory;
 		this.privateGroupFactory = privateGroupFactory;
 		this.clientHelper = clientHelper;
@@ -97,13 +99,20 @@ class MessageParserImpl implements MessageParser {
 	@Override
 	public InviteMessage parseInviteMessage(Message m, BdfList body)
 			throws FormatException {
-		String groupName = body.getString(1);
-		String creatorName = body.getString(2);
-		byte[] creatorPublicKey = body.getRaw(3);
-		byte[] salt = body.getRaw(4);
-		String message = body.getOptionalString(5);
-		byte[] signature = body.getRaw(6);
-		Author creator = authorFactory.createAuthor(creatorName,
+		// Message type, creator, group name, salt, optional message, signature
+		BdfList creatorList = body.getList(1);
+		String groupName = body.getString(2);
+		byte[] salt = body.getRaw(3);
+		String message = body.getOptionalString(4);
+		byte[] signature = body.getRaw(5);
+
+		// Format version, name, public key
+		int formatVersion = creatorList.getLong(0).intValue();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
+		String creatorName = creatorList.getString(1);
+		byte[] creatorPublicKey = creatorList.getRaw(2);
+
+		Author creator = authorFactory.createAuthor(formatVersion, creatorName,
 				creatorPublicKey);
 		PrivateGroup privateGroup = privateGroupFactory.createPrivateGroup(
 				groupName, creator, salt);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java
index 6795b0fb05..a2fc9d95db 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogMessageParserImpl.java
@@ -12,6 +12,8 @@ import org.briarproject.briar.api.blog.BlogFactory;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.identity.Author.FORMAT_VERSION;
+
 @Immutable
 @NotNullByDefault
 class BlogMessageParserImpl extends MessageParserImpl<Blog> {
@@ -28,12 +30,19 @@ class BlogMessageParserImpl extends MessageParserImpl<Blog> {
 	}
 
 	@Override
-	protected Blog createShareable(BdfList descriptor)
-			throws FormatException {
-		String name = descriptor.getString(0);
-		byte[] publicKey = descriptor.getRaw(1);
-		boolean rssFeed = descriptor.getBoolean(2);
-		Author author = authorFactory.createAuthor(name, publicKey);
+	protected Blog createShareable(BdfList descriptor) throws FormatException {
+		// Author, RSS
+		BdfList authorList = descriptor.getList(0);
+		boolean rssFeed = descriptor.getBoolean(1);
+
+		// Format version, name, public key
+		int formatVersion = authorList.getLong(0).intValue();
+		if (formatVersion != FORMAT_VERSION) throw new FormatException();
+		String name = authorList.getString(1);
+		byte[] publicKey = authorList.getRaw(2);
+
+		Author author = authorFactory.createAuthor(formatVersion, name,
+				publicKey);
 		if (rssFeed) return blogFactory.createFeedBlog(author);
 		else return blogFactory.createBlog(author);
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java
index f9b378ef78..1f64b02a3c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/BlogSharingValidator.java
@@ -5,19 +5,13 @@ import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.briar.api.blog.Blog;
 import org.briarproject.briar.api.blog.BlogFactory;
 
 import javax.annotation.concurrent.Immutable;
-import javax.inject.Inject;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 
 @Immutable
@@ -25,35 +19,24 @@ import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 class BlogSharingValidator extends SharingValidator {
 
 	private final BlogFactory blogFactory;
-	private final AuthorFactory authorFactory;
 
-	@Inject
 	BlogSharingValidator(MessageEncoder messageEncoder,
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock, BlogFactory blogFactory, AuthorFactory authorFactory) {
+			Clock clock, BlogFactory blogFactory) {
 		super(messageEncoder, clientHelper, metadataEncoder, clock);
 		this.blogFactory = blogFactory;
-		this.authorFactory = authorFactory;
 	}
 
 	@Override
 	protected GroupId validateDescriptor(BdfList descriptor)
 			throws FormatException {
-		checkSize(descriptor, 3);
-		String name = descriptor.getString(0);
-		checkLength(name, 1, MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey = descriptor.getRaw(1);
-		checkLength(publicKey, 1, MAX_PUBLIC_KEY_LENGTH);
-		boolean rssFeed = descriptor.getBoolean(2);
-
-		Author author = authorFactory.createAuthor(name, publicKey);
-		Blog blog;
-		if (rssFeed) {
-			blog = blogFactory.createFeedBlog(author);
-		} else {
-			blog = blogFactory.createBlog(author);
-		}
-		return blog.getId();
+		// Author, RSS
+		checkSize(descriptor, 2);
+		BdfList authorList = descriptor.getList(0);
+		boolean rssFeed = descriptor.getBoolean(1);
+		Author author = clientHelper.parseAndValidateAuthor(authorList);
+		if (rssFeed) return blogFactory.createFeedBlog(author).getId();
+		else return blogFactory.createBlog(author).getId();
 	}
 
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java
index ed1ca425cf..44ef0d821b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumMessageParserImpl.java
@@ -24,8 +24,8 @@ class ForumMessageParserImpl extends MessageParserImpl<Forum> {
 	}
 
 	@Override
-	protected Forum createShareable(BdfList descriptor)
-			throws FormatException {
+	protected Forum createShareable(BdfList descriptor) throws FormatException {
+		// Name, salt
 		String name = descriptor.getString(0);
 		byte[] salt = descriptor.getRaw(1);
 		return forumFactory.createForum(name, salt);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java
index 14853dfb18..dd66181d7f 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ForumSharingValidator.java
@@ -35,6 +35,7 @@ class ForumSharingValidator extends SharingValidator {
 	@Override
 	protected GroupId validateDescriptor(BdfList descriptor)
 			throws FormatException {
+		// Name, salt
 		checkSize(descriptor, 2);
 		String name = descriptor.getString(0);
 		checkLength(name, 1, MAX_FORUM_NAME_LENGTH);
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
index 7064696e86..e6a7976d58 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/ProtocolEngineImpl.java
@@ -112,9 +112,8 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 			throw new DbException(e); // Invalid group descriptor
 		}
 		long localTimestamp = Math.max(timestamp, getLocalTimestamp(s));
-		Message m = messageEncoder
-				.encodeInviteMessage(s.getContactGroupId(), localTimestamp,
-						s.getLastLocalMessageId(), descriptor, message);
+		Message m = messageEncoder.encodeInviteMessage(s.getContactGroupId(),
+				localTimestamp, s.getLastLocalMessageId(), descriptor, message);
 		sendMessage(txn, m, INVITE, s.getShareableId(), true);
 		return m;
 	}
@@ -143,7 +142,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (inviteId == null) throw new IllegalStateException();
 		markMessageAvailableToAnswer(txn, inviteId, false);
 		// Mark the invite message as accepted
-		markInvitationAccepted(txn, inviteId, true);
+		markInvitationAccepted(txn, inviteId);
 		// Send a ACCEPT message
 		Message sent = sendAcceptMessage(txn, s);
 		// Track the message
@@ -288,7 +287,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abortWithMessage(txn, s);
 		// Mark the invite message visible in the UI and (un)available to answer
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		markMessageAvailableToAnswer(txn, m.getId(), available);
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
@@ -312,7 +311,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abortWithMessage(txn, s);
 		// Mark the invite message visible in the UI and unavailable to answer
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		markMessageAvailableToAnswer(txn, m.getId(), false);
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
@@ -359,7 +358,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abortWithMessage(txn, s);
 		// Mark the response visible in the UI
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
 				m.getTimestamp(), false);
@@ -411,7 +410,7 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		if (!isValidDependency(s, m.getPreviousMessageId()))
 			return abortWithMessage(txn, s);
 		// Mark the response visible in the UI
-		markMessageVisibleInUi(txn, m.getId(), true);
+		markMessageVisibleInUi(txn, m.getId());
 		// Track the message
 		messageTracker.trackMessage(txn, m.getContactGroupId(),
 				m.getTimestamp(), false);
@@ -576,10 +575,10 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		}
 	}
 
-	private void markMessageVisibleInUi(Transaction txn, MessageId m,
-			boolean visible) throws DbException {
+	private void markMessageVisibleInUi(Transaction txn, MessageId m)
+			throws DbException {
 		BdfDictionary meta = new BdfDictionary();
-		messageEncoder.setVisibleInUi(meta, visible);
+		messageEncoder.setVisibleInUi(meta, true);
 		try {
 			clientHelper.mergeMessageMetadata(txn, m, meta);
 		} catch (FormatException e) {
@@ -587,10 +586,10 @@ abstract class ProtocolEngineImpl<S extends Shareable>
 		}
 	}
 
-	private void markInvitationAccepted(Transaction txn, MessageId m,
-			boolean accepted) throws DbException {
+	private void markInvitationAccepted(Transaction txn, MessageId m)
+			throws DbException {
 		BdfDictionary meta = new BdfDictionary();
-		messageEncoder.setInvitationAccepted(meta, accepted);
+		messageEncoder.setInvitationAccepted(meta, true);
 		try {
 			clientHelper.mergeMessageMetadata(txn, m, meta);
 		} catch (FormatException e) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
index 5d49e104d2..5d5910bc6c 100644
--- a/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/sharing/SharingModule.java
@@ -3,7 +3,6 @@ package org.briarproject.briar.sharing;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.contact.ContactManager;
 import org.briarproject.bramble.api.data.MetadataEncoder;
-import org.briarproject.bramble.api.identity.AuthorFactory;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.bramble.api.system.Clock;
@@ -59,10 +58,10 @@ public class SharingModule {
 	BlogSharingValidator provideBlogSharingValidator(
 			ValidationManager validationManager, MessageEncoder messageEncoder,
 			ClientHelper clientHelper, MetadataEncoder metadataEncoder,
-			Clock clock, BlogFactory blogFactory, AuthorFactory authorFactory) {
+			Clock clock, BlogFactory blogFactory) {
 		BlogSharingValidator validator =
 				new BlogSharingValidator(messageEncoder, clientHelper,
-						metadataEncoder, clock, blogFactory, authorFactory);
+						metadataEncoder, clock, blogFactory);
 		validationManager.registerMessageValidator(BlogSharingManager.CLIENT_ID,
 				validator);
 		return validator;
diff --git a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java
index b9a91694f4..faf3ddfe91 100644
--- a/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/test/TestDataCreatorImpl.java
@@ -190,8 +190,8 @@ public class TestDataCreatorImpl implements TestDataCreator {
 		KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
 		byte[] publicKey = keyPair.getPublic().getEncoded();
 		byte[] privateKey = keyPair.getPrivate().getEncoded();
-		return authorFactory
-				.createLocalAuthor(authorName, publicKey, privateKey);
+		return authorFactory.createLocalAuthor(authorName, publicKey,
+				privateKey);
 	}
 
 	private SecretKey getSecretKey() {
diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
index 49a50a219f..688a3e11ac 100644
--- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogManagerImplTest.java
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.sync.Group;
@@ -34,9 +33,8 @@ import org.junit.Test;
 import static org.briarproject.bramble.api.identity.Author.Status.NONE;
 import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
 import static org.briarproject.bramble.api.identity.Author.Status.VERIFIED;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.util.StringUtils.getRandomString;
@@ -44,6 +42,7 @@ import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_FORMAT_VERSION;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
@@ -89,9 +88,9 @@ public class BlogManagerImplTest extends BriarTestCase {
 		blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
 				metadataParser, blogFactory, blogPostFactory);
 
-		localAuthor1 = createLocalAuthor();
-		localAuthor2 = createLocalAuthor();
-		rssLocalAuthor = createLocalAuthor();
+		localAuthor1 = getLocalAuthor();
+		localAuthor2 = getLocalAuthor();
+		rssLocalAuthor = getLocalAuthor();
 		authorDict1 = authorToBdfDictionary(localAuthor1);
 		authorDict2 = authorToBdfDictionary(localAuthor2);
 		rssAuthorDict = authorToBdfDictionary(rssLocalAuthor);
@@ -822,13 +821,6 @@ public class BlogManagerImplTest extends BriarTestCase {
 		context.assertIsSatisfied();
 	}
 
-	private LocalAuthor createLocalAuthor() {
-		return new LocalAuthor(new AuthorId(getRandomId()),
-				getRandomString(MAX_AUTHOR_NAME_LENGTH),
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
-				getRandomBytes(123), System.currentTimeMillis());
-	}
-
 	private Blog createBlog(LocalAuthor localAuthor, boolean rssFeed) {
 		GroupId groupId = new GroupId(getRandomId());
 		Group group = new Group(groupId, CLIENT_ID, getRandomBytes(42));
@@ -838,6 +830,7 @@ public class BlogManagerImplTest extends BriarTestCase {
 	private BdfDictionary authorToBdfDictionary(Author a) {
 		return BdfDictionary.of(
 				new BdfEntry(KEY_AUTHOR_ID, a.getId()),
+				new BdfEntry(KEY_FORMAT_VERSION, a.getFormatVersion()),
 				new BdfEntry(KEY_AUTHOR_NAME, a.getName()),
 				new BdfEntry(KEY_PUBLIC_KEY, a.getPublicKey())
 		);
diff --git a/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java
index 3a7877a780..dfd733955b 100644
--- a/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/blog/BlogPostValidatorTest.java
@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupFactory;
 import org.briarproject.bramble.api.sync.GroupId;
@@ -16,8 +15,6 @@ import org.briarproject.bramble.api.sync.MessageFactory;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.system.SystemClock;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.blog.Blog;
 import org.briarproject.briar.api.blog.BlogFactory;
 import org.briarproject.briar.test.BriarTestCase;
@@ -28,11 +25,15 @@ import org.junit.Test;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_AUTHOR_NAME;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_COMMENT;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_FORMAT_VERSION;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
 import static org.briarproject.briar.api.blog.BlogConstants.KEY_PARENT_MSG_ID;
@@ -65,27 +66,25 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	private final BlogFactory blogFactory = context.mock(BlogFactory.class);
 	private final ClientHelper clientHelper = context.mock(ClientHelper.class);
 	private final Author author;
-	private final String body = StringUtils.getRandomString(42);
+	private final String body = getRandomString(42);
 
 	public BlogPostValidatorTest() {
-		GroupId groupId = new GroupId(TestUtils.getRandomId());
-		descriptor = TestUtils.getRandomBytes(42);
+		GroupId groupId = new GroupId(getRandomId());
+		descriptor = getRandomBytes(42);
 		group = new Group(groupId, CLIENT_ID, descriptor);
-		AuthorId authorId =
-				new AuthorId(TestUtils.getRandomBytes(AuthorId.LENGTH));
-		byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		author = new Author(authorId, "Author", publicKey);
+		author = getAuthor();
 		authorDict = BdfDictionary.of(
 				new BdfEntry(KEY_AUTHOR_ID, author.getId()),
+				new BdfEntry(KEY_FORMAT_VERSION, author.getFormatVersion()),
 				new BdfEntry(KEY_AUTHOR_NAME, author.getName()),
 				new BdfEntry(KEY_PUBLIC_KEY, author.getPublicKey())
 		);
 		blog = new Blog(group, author, false);
 		rssBlog = new Blog(group, author, true);
 
-		MessageId messageId = new MessageId(TestUtils.getRandomId());
+		MessageId messageId = new MessageId(getRandomId());
 		long timestamp = System.currentTimeMillis();
-		byte[] raw = TestUtils.getRandomBytes(123);
+		byte[] raw = getRandomBytes(123);
 		message = new Message(messageId, group.getId(), timestamp, raw);
 
 		MetadataEncoder metadataEncoder = context.mock(MetadataEncoder.class);
@@ -110,7 +109,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 
 	private void testValidateProperBlogPost(Blog b, boolean rssFeed)
 			throws IOException, GeneralSecurityException {
-		byte[] sigBytes = TestUtils.getRandomBytes(42);
+		byte[] sigBytes = getRandomBytes(42);
 		BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
 
 		BdfList signed = BdfList.of(b.getId(), message.getTimestamp(), body);
@@ -147,9 +146,9 @@ public class BlogPostValidatorTest extends BriarTestCase {
 			throws IOException, GeneralSecurityException {
 		// comment, parent_original_id, parent_id, signature
 		String comment = "This is a blog comment";
-		MessageId pOriginalId = new MessageId(TestUtils.getRandomId());
-		MessageId currentId = new MessageId(TestUtils.getRandomId());
-		byte[] sigBytes = TestUtils.getRandomBytes(42);
+		MessageId pOriginalId = new MessageId(getRandomId());
+		MessageId currentId = new MessageId(getRandomId());
+		byte[] sigBytes = getRandomBytes(42);
 		BdfList m = BdfList.of(COMMENT.getInt(), comment, pOriginalId,
 				currentId, sigBytes);
 
@@ -172,9 +171,9 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	public void testValidateProperEmptyBlogComment()
 			throws IOException, GeneralSecurityException {
 		// comment, parent_original_id, signature, parent_current_id
-		MessageId originalId = new MessageId(TestUtils.getRandomId());
-		MessageId currentId = new MessageId(TestUtils.getRandomId());
-		byte[] sigBytes = TestUtils.getRandomBytes(42);
+		MessageId originalId = new MessageId(getRandomId());
+		MessageId currentId = new MessageId(getRandomId());
+		byte[] sigBytes = getRandomBytes(42);
 		BdfList m = BdfList.of(COMMENT.getInt(), null, originalId, currentId,
 				sigBytes);
 
@@ -203,7 +202,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	private void testValidateProperWrappedPost(Blog b, boolean rssFeed)
 			throws IOException, GeneralSecurityException {
 		// group descriptor, timestamp, content, signature
-		byte[] sigBytes = TestUtils.getRandomBytes(42);
+		byte[] sigBytes = getRandomBytes(42);
 		BdfList m = BdfList.of(WRAPPED_POST.getInt(), descriptor,
 				message.getTimestamp(), body, sigBytes);
 
@@ -211,7 +210,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 		expectCrypto(b, SIGNING_LABEL_POST, signed, sigBytes);
 
 		BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
-		byte[] originalBody = TestUtils.getRandomBytes(42);
+		byte[] originalBody = getRandomBytes(42);
 
 		context.checking(new Expectations() {{
 			oneOf(groupFactory).createGroup(CLIENT_ID, CLIENT_VERSION,
@@ -241,10 +240,10 @@ public class BlogPostValidatorTest extends BriarTestCase {
 		// group descriptor, timestamp, comment, parent_original_id, signature,
 		// parent_current_id
 		String comment = "This is another comment";
-		MessageId originalId = new MessageId(TestUtils.getRandomId());
-		MessageId oldId = new MessageId(TestUtils.getRandomId());
-		byte[] sigBytes = TestUtils.getRandomBytes(42);
-		MessageId currentId = new MessageId(TestUtils.getRandomId());
+		MessageId originalId = new MessageId(getRandomId());
+		MessageId oldId = new MessageId(getRandomId());
+		byte[] sigBytes = getRandomBytes(42);
+		MessageId currentId = new MessageId(getRandomId());
 		BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
 				message.getTimestamp(), comment, originalId, oldId, sigBytes,
 				currentId);
@@ -255,7 +254,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 
 		BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
 				originalId, oldId, sigBytes);
-		byte[] originalBody = TestUtils.getRandomBytes(42);
+		byte[] originalBody = getRandomBytes(42);
 
 		context.checking(new Expectations() {{
 			oneOf(groupFactory).createGroup(CLIENT_ID, CLIENT_VERSION,
diff --git a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java
index 2f7953311f..d3e69e7ddf 100644
--- a/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/feed/FeedManagerImplTest.java
@@ -10,7 +10,6 @@ import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.Transaction;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupId;
@@ -38,6 +37,7 @@ import javax.net.SocketFactory;
 
 import okhttp3.Dns;
 
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.briar.api.feed.FeedConstants.KEY_FEEDS;
@@ -66,10 +66,7 @@ public class FeedManagerImplTest extends BrambleMockTestCase {
 	private final GroupId blogGroupId = new GroupId(getRandomId());
 	private final Group blogGroup =
 			new Group(blogGroupId, BlogManager.CLIENT_ID, getRandomBytes(42));
-	private final AuthorId authorId = new AuthorId(getRandomId());
-	private final LocalAuthor localAuthor =
-			new LocalAuthor(authorId, "author", getRandomBytes(2),
-					getRandomBytes(2), 0);
+	private final LocalAuthor localAuthor = getLocalAuthor();
 	private final Blog blog = new Blog(blogGroup, localAuthor, true);
 	private final Feed feed =
 			new Feed("http://example.org", blog, localAuthor, 0);
diff --git a/briar-core/src/test/java/org/briarproject/briar/forum/ForumPostValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/forum/ForumPostValidatorTest.java
index 51e29b23a0..bf514d0c52 100644
--- a/briar-core/src/test/java/org/briarproject/briar/forum/ForumPostValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/forum/ForumPostValidatorTest.java
@@ -6,21 +6,28 @@ import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.sync.InvalidMessageException;
 import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.bramble.test.TestUtils;
 import org.briarproject.bramble.test.ValidatorTestCase;
-import org.briarproject.bramble.util.StringUtils;
 import org.jmock.Expectations;
 import org.junit.Test;
 
 import java.security.GeneralSecurityException;
 import java.util.Collection;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.blog.BlogConstants.KEY_READ;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_AUTHOR;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_FORMAT_VERSION;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_ID;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_NAME;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_PARENT;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_PUBLIC_KEY;
+import static org.briarproject.briar.api.forum.ForumConstants.KEY_TIMESTAMP;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.briarproject.briar.api.forum.ForumPostFactory.SIGNING_LABEL_POST;
 import static org.junit.Assert.assertArrayEquals;
@@ -29,51 +36,42 @@ import static org.junit.Assert.assertFalse;
 
 public class ForumPostValidatorTest extends ValidatorTestCase {
 
-	private final MessageId parentId = new MessageId(TestUtils.getRandomId());
-	private final String authorName =
-			StringUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
-	private final byte[] authorPublicKey =
-			TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-	private final BdfList authorList = BdfList.of(authorName, authorPublicKey);
-	private final String content =
-			StringUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH);
-	private final byte[] signature =
-			TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
-	private final AuthorId authorId = new AuthorId(TestUtils.getRandomId());
-	private final Author author =
-			new Author(authorId, authorName, authorPublicKey);
+	private final MessageId parentId = new MessageId(getRandomId());
+	private final String content = getRandomString(MAX_FORUM_POST_BODY_LENGTH);
+	private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
+	private final Author author = getAuthor();
+	private final String authorName = author.getName();
+	private final byte[] authorPublicKey = author.getPublicKey();
+	private final BdfList authorList = BdfList.of(author.getFormatVersion(),
+			authorName, authorPublicKey);
 	private final BdfList signedWithParent = BdfList.of(groupId, timestamp,
 			parentId.getBytes(), authorList, content);
 	private final BdfList signedWithoutParent = BdfList.of(groupId, timestamp,
 			null, authorList, content);
 
+	private final ForumPostValidator v = new ForumPostValidator(clientHelper,
+			metadataEncoder, clock);
+
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortBody() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongBody() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content, signature, 123));
 	}
 
 	@Test
 	public void testAcceptsNullParentId() throws Exception {
+		expectCreateAuthor();
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
 			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
 					authorPublicKey, signedWithoutParent);
 		}});
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		BdfMessageContext messageContext = v.validateMessage(message, group,
 				BdfList.of(null, authorList, content, signature));
 		assertExpectedContext(messageContext, false, authorName);
@@ -81,177 +79,58 @@ public class ForumPostValidatorTest extends ValidatorTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonRawParentId() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(123, authorList, content, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortParentId() throws Exception {
-		byte[] invalidParentId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
+		byte[] invalidParentId = getRandomBytes(UniqueId.LENGTH - 1);
 		v.validateMessage(message, group,
 				BdfList.of(invalidParentId, authorList, content, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongParentId() throws Exception {
-		byte[] invalidParentId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
+		byte[] invalidParentId = getRandomBytes(UniqueId.LENGTH + 1);
 		v.validateMessage(message, group,
 				BdfList.of(invalidParentId, authorList, content, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNullAuthorList() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, null, content, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonListAuthorList() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, 123, content, signature));
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsTooShortAuthorList() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, new BdfList(), content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongAuthorList() throws Exception {
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, BdfList.of(1, 2, 3), content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNullAuthorName() throws Exception {
-		BdfList invalidAuthorList = BdfList.of(null, authorPublicKey);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNonStringAuthorName() throws Exception {
-		BdfList invalidAuthorList = BdfList.of(123, authorPublicKey);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooShortAuthorName() throws Exception {
-		BdfList invalidAuthorList = BdfList.of("", authorPublicKey);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
-	}
-
-	@Test
-	public void testAcceptsMinLengthAuthorName() throws Exception {
-		String shortAuthorName = StringUtils.getRandomString(1);
-		BdfList shortNameAuthorList =
-				BdfList.of(shortAuthorName, authorPublicKey);
-		Author shortNameAuthor =
-				new Author(authorId, shortAuthorName, authorPublicKey);
-		BdfList signedWithShortNameAuthor = BdfList.of(groupId, timestamp,
-				parentId.getBytes(), shortNameAuthorList, content);
-
+	public void testRejectsInvalidAuthor() throws Exception {
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(shortAuthorName, authorPublicKey);
-			will(returnValue(shortNameAuthor));
-			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
-					authorPublicKey, signedWithShortNameAuthor);
+			oneOf(clientHelper).parseAndValidateAuthor(authorList);
+			will(throwException(new FormatException()));
 		}});
-
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(parentId, shortNameAuthorList, content, signature));
-		assertExpectedContext(messageContext, true, shortAuthorName);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongAuthorName() throws Exception {
-		String invalidAuthorName =
-				StringUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH + 1);
-		BdfList invalidAuthorList =
-				BdfList.of(invalidAuthorName, authorPublicKey);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNullAuthorPublicKey() throws Exception {
-		BdfList invalidAuthorList = BdfList.of(authorName, null);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNonRawAuthorPublicKey() throws Exception {
-		BdfList invalidAuthorList = BdfList.of(authorName, 123);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongAuthorPublicKey() throws Exception {
-		byte[] invalidAuthorPublicKey =
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
-		BdfList invalidAuthorList =
-				BdfList.of(authorName, invalidAuthorPublicKey);
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
-		v.validateMessage(message, group,
-				BdfList.of(parentId, invalidAuthorList, content, signature));
+				BdfList.of(parentId, authorList, content, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNullContent() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
-		}});
+		expectCreateAuthor();
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, null, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonStringContent() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
-		}});
+		expectCreateAuthor();
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, 123, signature));
 	}
@@ -262,15 +141,12 @@ public class ForumPostValidatorTest extends ValidatorTestCase {
 		BdfList signedWithShortContent = BdfList.of(groupId, timestamp,
 				parentId.getBytes(), authorList, shortContent);
 
+		expectCreateAuthor();
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
 			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
 					authorPublicKey, signedWithShortContent);
 		}});
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		BdfMessageContext messageContext = v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, shortContent, signature));
 		assertExpectedContext(messageContext, true, authorName);
@@ -278,58 +154,36 @@ public class ForumPostValidatorTest extends ValidatorTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongContent() throws Exception {
-		String invalidContent =
-				StringUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH + 1);
+		String invalidContent = getRandomString(MAX_FORUM_POST_BODY_LENGTH + 1);
 
-		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
-		}});
+		expectCreateAuthor();
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, invalidContent, signature));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNullSignature() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
-		}});
+		expectCreateAuthor();
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content, null));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonRawSignature() throws Exception {
-		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
-		}});
+		expectCreateAuthor();
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content, 123));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongSignature() throws Exception {
-		byte[] invalidSignature =
-				TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH + 1);
+		byte[] invalidSignature = getRandomBytes(MAX_SIGNATURE_LENGTH + 1);
 
-		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
-		}});
+		expectCreateAuthor();
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content, invalidSignature));
 	}
@@ -337,16 +191,13 @@ public class ForumPostValidatorTest extends ValidatorTestCase {
 	@Test(expected = FormatException.class)
 	public void testRejectsIfVerifyingSignatureThrowsFormatException()
 			throws Exception {
+		expectCreateAuthor();
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
 			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
 					authorPublicKey, signedWithParent);
 			will(throwException(new FormatException()));
 		}});
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content, signature));
 	}
@@ -354,39 +205,45 @@ public class ForumPostValidatorTest extends ValidatorTestCase {
 	@Test(expected = InvalidMessageException.class)
 	public void testRejectsIfVerifyingSignatureThrowsGeneralSecurityException()
 			throws Exception {
+		expectCreateAuthor();
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(authorName, authorPublicKey);
-			will(returnValue(author));
 			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
 					authorPublicKey, signedWithParent);
 			will(throwException(new GeneralSecurityException()));
 		}});
 
-		ForumPostValidator v = new ForumPostValidator(authorFactory,
-				clientHelper, metadataEncoder, clock);
 		v.validateMessage(message, group,
 				BdfList.of(parentId, authorList, content, signature));
 	}
 
+	private void expectCreateAuthor() throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(clientHelper).parseAndValidateAuthor(authorList);
+			will(returnValue(author));
+		}});
+	}
+
 	private void assertExpectedContext(BdfMessageContext messageContext,
 			boolean hasParent, String authorName) throws FormatException {
 		BdfDictionary meta = messageContext.getDictionary();
 		Collection<MessageId> dependencies = messageContext.getDependencies();
 		if (hasParent) {
 			assertEquals(4, meta.size());
-			assertArrayEquals(parentId.getBytes(), meta.getRaw("parent"));
+			assertArrayEquals(parentId.getBytes(), meta.getRaw(KEY_PARENT));
 			assertEquals(1, dependencies.size());
 			assertEquals(parentId, dependencies.iterator().next());
 		} else {
 			assertEquals(3, meta.size());
 			assertEquals(0, dependencies.size());
 		}
-		assertEquals(timestamp, meta.getLong("timestamp").longValue());
-		assertFalse(meta.getBoolean("read"));
-		BdfDictionary authorMeta = meta.getDictionary("author");
-		assertEquals(3, authorMeta.size());
-		assertArrayEquals(authorId.getBytes(), authorMeta.getRaw("id"));
-		assertEquals(authorName, authorMeta.getString("name"));
-		assertArrayEquals(authorPublicKey, authorMeta.getRaw("publicKey"));
+		assertEquals(timestamp, meta.getLong(KEY_TIMESTAMP).longValue());
+		assertFalse(meta.getBoolean(KEY_READ));
+		BdfDictionary authorMeta = meta.getDictionary(KEY_AUTHOR);
+		assertEquals(4, authorMeta.size());
+		assertArrayEquals(author.getId().getBytes(), authorMeta.getRaw(KEY_ID));
+		assertEquals(author.getFormatVersion(),
+				authorMeta.getLong(KEY_FORMAT_VERSION).intValue());
+		assertEquals(authorName, authorMeta.getString(KEY_NAME));
+		assertArrayEquals(authorPublicKey, authorMeta.getRaw(KEY_PUBLIC_KEY));
 	}
 }
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java
index 2144b49799..72d178a7f7 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroduceeManagerTest.java
@@ -25,7 +25,6 @@ import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.test.TestUtils;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.introduction.IntroduceeProtocolState;
 import org.briarproject.briar.test.BriarTestCase;
@@ -40,6 +39,10 @@ import java.security.SecureRandom;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.briar.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
@@ -125,47 +128,41 @@ public class IntroduceeManagerTest extends BriarTestCase {
 				authorFactory, contactManager, identityManager,
 				introductionGroupFactory);
 
-		AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
-		Author author0 = new Author(authorId0, "Introducer",
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
-		AuthorId localAuthorId = new AuthorId(TestUtils.getRandomId());
+		Author author0 = getAuthor();
+		AuthorId localAuthorId = new AuthorId(getRandomId());
 		ContactId contactId0 = new ContactId(234);
 		introducer =
 				new Contact(contactId0, author0, localAuthorId, true, true);
 
-		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
-		Author author1 = new Author(authorId1, "Introducee1",
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
-		AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
+		Author author1 = getAuthor();
+		AuthorId localAuthorId1 = new AuthorId(getRandomId());
 		ContactId contactId1 = new ContactId(234);
 		introducee1 =
 				new Contact(contactId1, author1, localAuthorId1, true, true);
 
-		AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
-		Author author2 = new Author(authorId2, "Introducee2",
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
+		Author author2 = getAuthor();
 		ContactId contactId2 = new ContactId(235);
 		introducee2 =
 				new Contact(contactId2, author2, localAuthorId, true, true);
 
 		ClientId clientId = IntroductionManagerImpl.CLIENT_ID;
-		localGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
+		localGroup1 = new Group(new GroupId(getRandomId()),
 				clientId, new byte[0]);
-		introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
+		introductionGroup1 = new Group(new GroupId(getRandomId()),
 				clientId, new byte[0]);
 
-		sessionId = new SessionId(TestUtils.getRandomId());
+		sessionId = new SessionId(getRandomId());
 		localStateMessage = new Message(
-				new MessageId(TestUtils.getRandomId()),
+				new MessageId(getRandomId()),
 				localGroup1.getId(),
 				time,
-				TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
+				getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
 		);
 		message1 = new Message(
-				new MessageId(TestUtils.getRandomId()),
+				new MessageId(getRandomId()),
 				introductionGroup1.getId(),
 				time,
-				TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
+				getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
 		);
 
 		txn = new Transaction(null, false);
@@ -218,7 +215,7 @@ public class IntroduceeManagerTest extends BriarTestCase {
 		// turn request message into a response
 		msg.put(ACCEPT, true);
 		msg.put(TIME, time);
-		msg.put(E_PUBLIC_KEY, TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
+		msg.put(E_PUBLIC_KEY, getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
 		msg.put(TRANSPORT, new BdfDictionary());
 
 		context.checking(new Expectations() {{
@@ -251,13 +248,13 @@ public class IntroduceeManagerTest extends BriarTestCase {
 		// prepare state for incoming ACK
 		state.put(STATE, IntroduceeProtocolState.AWAIT_ACK.ordinal());
 		state.put(ADDED_CONTACT_ID, 2);
-		byte[] nonce = TestUtils.getRandomBytes(42);
+		byte[] nonce = getRandomBytes(42);
 		state.put(NONCE, nonce);
 		state.put(PUBLIC_KEY, introducee2.getAuthor().getPublicKey());
 
 		// create incoming ACK message
-		byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
-		byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
+		byte[] mac = getRandomBytes(MAC_LENGTH);
+		byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
 		BdfDictionary ack = BdfDictionary.of(
 				new BdfEntry(TYPE, TYPE_ACK),
 				new BdfEntry(SESSION_ID, sessionId),
@@ -288,8 +285,8 @@ public class IntroduceeManagerTest extends BriarTestCase {
 			throws FormatException, DbException, GeneralSecurityException {
 
 		byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
-		byte[] nonce = TestUtils.getRandomBytes(MAC_LENGTH);
-		byte[] sig = TestUtils.getRandomBytes(MAC_LENGTH);
+		byte[] nonce = getRandomBytes(MAC_LENGTH);
+		byte[] sig = getRandomBytes(MAC_LENGTH);
 
 		BdfDictionary state = new BdfDictionary();
 		state.put(PUBLIC_KEY, publicKeyBytes);
@@ -311,10 +308,9 @@ public class IntroduceeManagerTest extends BriarTestCase {
 
 		byte[] publicKeyBytes = introducee2.getAuthor().getPublicKey();
 		BdfDictionary tp = BdfDictionary.of(new BdfEntry("fake", "fake"));
-		byte[] ePublicKeyBytes =
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
-		SecretKey macKey = TestUtils.getSecretKey();
+		byte[] ePublicKeyBytes = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+		byte[] mac = getRandomBytes(MAC_LENGTH);
+		SecretKey macKey = getSecretKey();
 
 		// move state to where it would be after an ACK arrived
 		BdfDictionary state = new BdfDictionary();
@@ -325,7 +321,7 @@ public class IntroduceeManagerTest extends BriarTestCase {
 		state.put(MAC, mac);
 		state.put(MAC_KEY, macKey.getBytes());
 
-		byte[] signBytes = TestUtils.getRandomBytes(42);
+		byte[] signBytes = getRandomBytes(42);
 		context.checking(new Expectations() {{
 			oneOf(clientHelper).toByteArray(
 					BdfList.of(publicKeyBytes, ePublicKeyBytes, tp, time));
@@ -348,7 +344,7 @@ public class IntroduceeManagerTest extends BriarTestCase {
 			oneOf(cryptoComponent).mac(with(MAC_LABEL),
 					with(samePropertyValuesAs(macKey)),
 					with(array(equal(signBytes))));
-			will(returnValue(TestUtils.getRandomBytes(MAC_LENGTH)));
+			will(returnValue(getRandomBytes(MAC_LENGTH)));
 		}});
 		try {
 			introduceeManager.verifyMac(state);
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java
index 93b584ae7c..d56cd58637 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroducerManagerTest.java
@@ -18,7 +18,6 @@ import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
-import org.briarproject.bramble.test.TestUtils;
 import org.briarproject.briar.test.BriarTestCase;
 import org.jmock.Expectations;
 import org.jmock.Mockery;
@@ -27,7 +26,9 @@ import org.junit.Test;
 
 import java.security.SecureRandom;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.briar.api.introduction.IntroducerProtocolState.AWAIT_RESPONSES;
 import static org.briarproject.briar.api.introduction.IntroducerProtocolState.PREPARE_REQUESTS;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.AUTHOR_ID_1;
@@ -80,27 +81,23 @@ public class IntroducerManagerTest extends BriarTestCase {
 				new IntroducerManager(messageSender, clientHelper, clock,
 						cryptoComponent, introductionGroupFactory);
 
-		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
-		Author author1 = new Author(authorId1, "Introducee1",
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
-		AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
+		Author author1 = getAuthor();
+		AuthorId localAuthorId1 = new AuthorId(getRandomId());
 		ContactId contactId1 = new ContactId(234);
 		introducee1 =
 				new Contact(contactId1, author1, localAuthorId1, true, true);
 
-		AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
-		Author author2 = new Author(authorId2, "Introducee2",
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
-		AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId());
+		Author author2 = getAuthor();
+		AuthorId localAuthorId2 = new AuthorId(getRandomId());
 		ContactId contactId2 = new ContactId(235);
 		introducee2 =
 				new Contact(contactId2, author2, localAuthorId2, true, true);
 
-		localGroup0 = new Group(new GroupId(TestUtils.getRandomId()),
+		localGroup0 = new Group(new GroupId(getRandomId()),
 				getClientId(), new byte[0]);
-		introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
+		introductionGroup1 = new Group(new GroupId(getRandomId()),
 				getClientId(), new byte[0]);
-		introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
+		introductionGroup2 = new Group(new GroupId(getRandomId()),
 				getClientId(), new byte[0]);
 
 		context.assertIsSatisfied();
@@ -113,8 +110,8 @@ public class IntroducerManagerTest extends BriarTestCase {
 		context.setImposteriser(ClassImposteriser.INSTANCE);
 		SecureRandom secureRandom = context.mock(SecureRandom.class);
 		Bytes salt = new Bytes(new byte[64]);
-		Message msg = new Message(new MessageId(TestUtils.getRandomId()),
-				localGroup0.getId(), time, TestUtils.getRandomBytes(64));
+		Message msg = new Message(new MessageId(getRandomId()),
+				localGroup0.getId(), time, getRandomBytes(64));
 		BdfDictionary state = new BdfDictionary();
 		state.put(SESSION_ID, msg.getId());
 		state.put(STORAGE_ID, msg.getId());
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
index 896bdc2a31..ff4f28bb7b 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
@@ -26,8 +26,6 @@ import org.briarproject.bramble.api.sync.Group;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.TestDatabaseModule;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.introduction.IntroductionManager;
 import org.briarproject.briar.api.introduction.IntroductionMessage;
@@ -55,6 +53,8 @@ import javax.inject.Inject;
 
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.client.MessageQueueManager.QUEUE_STATE_KEY;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_MAC_KEY_LABEL;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.ALICE_NONCE_LABEL;
@@ -485,9 +485,8 @@ public class IntroductionIntegrationTest
 				new BdfEntry(TYPE, TYPE_REQUEST),
 				new BdfEntry(SESSION_ID, sessionId),
 				new BdfEntry(GROUP_ID, group.getId()),
-				new BdfEntry(NAME, StringUtils.getRandomString(42)),
-				new BdfEntry(PUBLIC_KEY,
-						TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
+				new BdfEntry(NAME, getRandomString(42)),
+				new BdfEntry(PUBLIC_KEY, getRandomBytes(MAX_PUBLIC_KEY_LENGTH))
 		);
 
 		// reset request received state
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java
index 7d06c94d0f..ef05fe8a2e 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionManagerImplTest.java
@@ -19,8 +19,6 @@ import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.sync.MessageStatus;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.client.MessageTracker;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.test.BriarTestCase;
@@ -33,8 +31,11 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_1;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID_2;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.ROLE;
@@ -55,7 +56,7 @@ public class IntroductionManagerImplTest extends BriarTestCase {
 	private final ClientHelper clientHelper;
 	private final MessageTracker messageTracker;
 	private final IntroductionGroupFactory introductionGroupFactory;
-	private final SessionId sessionId = new SessionId(TestUtils.getRandomId());
+	private final SessionId sessionId = new SessionId(getRandomId());
 	private final MessageId storageId = new MessageId(sessionId.getBytes());
 	private final long time = 42L;
 	private final Contact introducee1;
@@ -66,33 +67,29 @@ public class IntroductionManagerImplTest extends BriarTestCase {
 	private Transaction txn;
 
 	public IntroductionManagerImplTest() {
-		AuthorId authorId1 = new AuthorId(TestUtils.getRandomId());
-		Author author1 = new Author(authorId1, "Introducee1",
-				new byte[MAX_PUBLIC_KEY_LENGTH]);
-		AuthorId localAuthorId1 = new AuthorId(TestUtils.getRandomId());
+		Author author1 = getAuthor();
+		AuthorId localAuthorId1 = new AuthorId(getRandomId());
 		ContactId contactId1 = new ContactId(234);
 		introducee1 =
 				new Contact(contactId1, author1, localAuthorId1, true, true);
 
-		AuthorId authorId2 = new AuthorId(TestUtils.getRandomId());
-		Author author2 = new Author(authorId2, "Introducee2",
-				new byte[MAX_PUBLIC_KEY_LENGTH]);
-		AuthorId localAuthorId2 = new AuthorId(TestUtils.getRandomId());
+		Author author2 = getAuthor();
+		AuthorId localAuthorId2 = new AuthorId(getRandomId());
 		ContactId contactId2 = new ContactId(235);
 		introducee2 =
 				new Contact(contactId2, author2, localAuthorId2, true, true);
 
-		ClientId clientId = new ClientId(StringUtils.getRandomString(5));
-		introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
+		ClientId clientId = new ClientId(getRandomString(5));
+		introductionGroup1 = new Group(new GroupId(getRandomId()),
 				clientId, new byte[0]);
-		introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
+		introductionGroup2 = new Group(new GroupId(getRandomId()),
 				clientId, new byte[0]);
 
 		message1 = new Message(
-				new MessageId(TestUtils.getRandomId()),
+				new MessageId(getRandomId()),
 				introductionGroup1.getId(),
 				time,
-				TestUtils.getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
+				getRandomBytes(MESSAGE_HEADER_LENGTH + 1)
 		);
 
 		// mock ALL THE THINGS!!!
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java
index b37f975f26..afe880100f 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java
@@ -6,6 +6,7 @@ import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.data.MetadataEncoder;
+import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.bramble.api.sync.ClientId;
 import org.briarproject.bramble.api.sync.Group;
@@ -14,20 +15,20 @@ import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.system.SystemClock;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.test.BriarTestCase;
 import org.jmock.Mockery;
 import org.junit.Test;
 
-import java.io.IOException;
-
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.bramble.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.GROUP_ID;
@@ -59,14 +60,14 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	private final Clock clock = new SystemClock();
 
 	public IntroductionValidatorTest() {
-		GroupId groupId = new GroupId(TestUtils.getRandomId());
-		ClientId clientId = new ClientId(StringUtils.getRandomString(5));
-		byte[] descriptor = TestUtils.getRandomBytes(12);
+		GroupId groupId = new GroupId(getRandomId());
+		ClientId clientId = new ClientId(getRandomString(5));
+		byte[] descriptor = getRandomBytes(12);
 		group = new Group(groupId, clientId, descriptor);
 
-		MessageId messageId = new MessageId(TestUtils.getRandomId());
+		MessageId messageId = new MessageId(getRandomId());
 		long timestamp = System.currentTimeMillis();
-		byte[] raw = TestUtils.getRandomBytes(123);
+		byte[] raw = getRandomBytes(123);
 		message = new Message(messageId, group.getId(), timestamp, raw);
 
 
@@ -82,20 +83,17 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	//
 
 	@Test
-	public void testValidateProperIntroductionRequest() throws IOException {
-		byte[] sessionId = TestUtils.getRandomId();
-		String name = StringUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey =
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		String text =
-				StringUtils.getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
+	public void testValidateProperIntroductionRequest() throws Exception {
+		byte[] sessionId = getRandomId();
+		String name = getRandomString(MAX_AUTHOR_NAME_LENGTH);
+		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+		String text = getRandomString(MAX_INTRODUCTION_MESSAGE_LENGTH);
 
 		BdfList body = BdfList.of(TYPE_REQUEST, sessionId,
 				name, publicKey, text);
 
 		BdfDictionary result =
-				validator.validateMessage(message, group, body)
-						.getDictionary();
+				validator.validateMessage(message, group, body).getDictionary();
 
 		assertEquals(Long.valueOf(TYPE_REQUEST), result.getLong(TYPE));
 		assertEquals(sessionId, result.getRaw(SESSION_ID));
@@ -106,7 +104,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	@Test(expected = FormatException.class)
-	public void testValidateIntroductionRequestWithNoName() throws IOException {
+	public void testValidateIntroductionRequestWithNoName() throws Exception {
 		BdfDictionary msg = getValidIntroductionRequest();
 
 		// no NAME is message
@@ -118,8 +116,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	@Test(expected = FormatException.class)
-	public void testValidateIntroductionRequestWithLongName()
-			throws IOException {
+	public void testValidateIntroductionRequestWithLongName() throws Exception {
 		// too long NAME in message
 		BdfDictionary msg = getValidIntroductionRequest();
 		msg.put(NAME, msg.get(NAME) + "x");
@@ -132,7 +129,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testValidateIntroductionRequestWithWrongType()
-			throws IOException {
+			throws Exception {
 		// wrong message type
 		BdfDictionary msg = getValidIntroductionRequest();
 		msg.put(TYPE, 324234);
@@ -143,17 +140,16 @@ public class IntroductionValidatorTest extends BriarTestCase {
 		validator.validateMessage(message, group, body);
 	}
 
-	private BdfDictionary getValidIntroductionRequest() throws FormatException {
-		byte[] sessionId = TestUtils.getRandomId();
-		String name = StringUtils.getRandomString(MAX_AUTHOR_NAME_LENGTH);
-		byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		String text = StringUtils.getRandomString(MAX_MESSAGE_BODY_LENGTH);
+	private BdfDictionary getValidIntroductionRequest() throws Exception {
+		byte[] sessionId = getRandomId();
+		Author author = getAuthor();
+		String text = getRandomString(MAX_MESSAGE_BODY_LENGTH);
 
 		BdfDictionary msg = new BdfDictionary();
 		msg.put(TYPE, TYPE_REQUEST);
 		msg.put(SESSION_ID, sessionId);
-		msg.put(NAME, name);
-		msg.put(PUBLIC_KEY, publicKey);
+		msg.put(NAME, author.getName());
+		msg.put(PUBLIC_KEY, author.getPublicKey());
 		msg.put(MSG, text);
 
 		return msg;
@@ -164,16 +160,16 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	//
 
 	@Test
-	public void testValidateIntroductionAcceptResponse() throws IOException {
-		byte[] groupId = TestUtils.getRandomId();
-		byte[] sessionId = TestUtils.getRandomId();
+	public void testValidateIntroductionAcceptResponse() throws Exception {
+		byte[] groupId = getRandomId();
+		byte[] sessionId = getRandomId();
 		long time = clock.currentTimeMillis();
-		byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		String transportId = StringUtils
-				.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
+		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+		String transportId =
+				getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
 		BdfDictionary tProps = BdfDictionary.of(
-				new BdfEntry(StringUtils.getRandomString(MAX_PROPERTY_LENGTH),
-						StringUtils.getRandomString(MAX_PROPERTY_LENGTH))
+				new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH),
+						getRandomString(MAX_PROPERTY_LENGTH))
 		);
 		BdfDictionary tp = BdfDictionary.of(
 				new BdfEntry(transportId, tProps)
@@ -204,8 +200,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testValidateIntroductionDeclineResponse()
-			throws IOException {
+	public void testValidateIntroductionDeclineResponse() throws Exception {
 		BdfDictionary msg = getValidIntroductionResponse(false);
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
 				msg.getBoolean(ACCEPT));
@@ -219,7 +214,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testValidateIntroductionResponseWithoutAccept()
-			throws IOException {
+			throws Exception {
 		BdfDictionary msg = getValidIntroductionResponse(false);
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
 
@@ -228,11 +223,11 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testValidateIntroductionResponseWithBrokenTp()
-			throws IOException {
+			throws Exception {
 		BdfDictionary msg = getValidIntroductionResponse(true);
 		BdfDictionary tp = msg.getDictionary(TRANSPORT);
-		tp.put(StringUtils
-				.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X");
+		tp.put(
+				getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH), "X");
 		msg.put(TRANSPORT, tp);
 
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
@@ -244,7 +239,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testValidateIntroductionResponseWithoutPublicKey()
-			throws IOException {
+			throws Exception {
 		BdfDictionary msg = getValidIntroductionResponse(true);
 
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
@@ -255,17 +250,17 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	private BdfDictionary getValidIntroductionResponse(boolean accept)
-			throws FormatException {
+			throws Exception {
 
-		byte[] groupId = TestUtils.getRandomId();
-		byte[] sessionId = TestUtils.getRandomId();
+		byte[] groupId = getRandomId();
+		byte[] sessionId = getRandomId();
 		long time = clock.currentTimeMillis();
-		byte[] publicKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-		String transportId = StringUtils
-				.getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
+		byte[] publicKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+		String transportId =
+				getRandomString(TransportId.MAX_TRANSPORT_ID_LENGTH);
 		BdfDictionary tProps = BdfDictionary.of(
-				new BdfEntry(StringUtils.getRandomString(MAX_PROPERTY_LENGTH),
-						StringUtils.getRandomString(MAX_PROPERTY_LENGTH))
+				new BdfEntry(getRandomString(MAX_PROPERTY_LENGTH),
+						getRandomString(MAX_PROPERTY_LENGTH))
 		);
 		BdfDictionary tp = BdfDictionary.of(
 				new BdfEntry(transportId, tProps)
@@ -290,10 +285,10 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	//
 
 	@Test
-	public void testValidateProperIntroductionAck() throws IOException {
-		byte[] sessionId = TestUtils.getRandomId();
-		byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
-		byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
+	public void testValidateProperIntroductionAck() throws Exception {
+		byte[] sessionId = getRandomId();
+		byte[] mac = getRandomBytes(MAC_LENGTH);
+		byte[] sig = getRandomBytes(MAX_SIGNATURE_LENGTH);
 		BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
 
 		BdfDictionary result =
@@ -307,11 +302,11 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	@Test(expected = FormatException.class)
-	public void testValidateTooLongIntroductionAck() throws IOException {
+	public void testValidateTooLongIntroductionAck() throws Exception {
 		BdfDictionary msg = BdfDictionary.of(
 				new BdfEntry(TYPE, TYPE_ACK),
-				new BdfEntry(SESSION_ID, TestUtils.getRandomId()),
-				new BdfEntry("garbage", StringUtils.getRandomString(255))
+				new BdfEntry(SESSION_ID, getRandomId()),
+				new BdfEntry("garbage", getRandomString(255))
 		);
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
 				msg.getString("garbage"));
@@ -321,7 +316,7 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testValidateIntroductionAckWithLongSessionId()
-			throws IOException {
+			throws Exception {
 		BdfDictionary msg = BdfDictionary.of(
 				new BdfEntry(TYPE, TYPE_ACK),
 				new BdfEntry(SESSION_ID, new byte[SessionId.LENGTH + 1])
@@ -336,8 +331,8 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	//
 
 	@Test
-	public void testValidateProperIntroductionAbort() throws IOException {
-		byte[] sessionId = TestUtils.getRandomId();
+	public void testValidateProperIntroductionAbort() throws Exception {
+		byte[] sessionId = getRandomId();
 
 		BdfDictionary msg = new BdfDictionary();
 		msg.put(TYPE, TYPE_ABORT);
@@ -354,11 +349,11 @@ public class IntroductionValidatorTest extends BriarTestCase {
 	}
 
 	@Test(expected = FormatException.class)
-	public void testValidateTooLongIntroductionAbort() throws IOException {
+	public void testValidateTooLongIntroductionAbort() throws Exception {
 		BdfDictionary msg = BdfDictionary.of(
 				new BdfEntry(TYPE, TYPE_ABORT),
-				new BdfEntry(SESSION_ID, TestUtils.getRandomId()),
-				new BdfEntry("garbage", StringUtils.getRandomString(255))
+				new BdfEntry(SESSION_ID, getRandomId()),
+				new BdfEntry("garbage", getRandomString(255))
 		);
 		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID),
 				msg.getString("garbage"));
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
index 92d4851420..b56d42d7fa 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/MessageSizeIntegrationTest.java
@@ -8,9 +8,6 @@ import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.system.SystemModule;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
-import org.briarproject.briar.api.forum.ForumConstants;
 import org.briarproject.briar.api.forum.ForumPost;
 import org.briarproject.briar.api.forum.ForumPostFactory;
 import org.briarproject.briar.api.messaging.PrivateMessage;
@@ -22,8 +19,9 @@ import javax.inject.Inject;
 
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
 import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_BODY_LENGTH;
 import static org.junit.Assert.assertTrue;
@@ -47,12 +45,11 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testPrivateMessageFitsIntoPacket() throws Exception {
+	public void testPrivateMessageFitsIntoRecord() throws Exception {
 		// Create a maximum-length private message
 		GroupId groupId = new GroupId(getRandomId());
 		long timestamp = Long.MAX_VALUE;
-		String body =
-				StringUtils.fromUtf8(new byte[MAX_PRIVATE_MESSAGE_BODY_LENGTH]);
+		String body = getRandomString(MAX_PRIVATE_MESSAGE_BODY_LENGTH);
 		PrivateMessage message = privateMessageFactory.createPrivateMessage(
 				groupId, timestamp, body);
 		// Check the size of the serialised message
@@ -63,27 +60,25 @@ public class MessageSizeIntegrationTest extends BriarTestCase {
 	}
 
 	@Test
-	public void testForumPostFitsIntoPacket() throws Exception {
+	public void testForumPostFitsIntoRecord() throws Exception {
 		// Create a maximum-length author
-		String authorName = StringUtils.getRandomString(
-				MAX_AUTHOR_NAME_LENGTH);
+		int formatVersion = Integer.MAX_VALUE;
+		String authorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
 		byte[] authorPublic = new byte[MAX_PUBLIC_KEY_LENGTH];
 		PrivateKey privateKey = crypto.generateSignatureKeyPair().getPrivate();
-		LocalAuthor author = authorFactory
-				.createLocalAuthor(authorName, authorPublic,
-						privateKey.getEncoded());
+		LocalAuthor author = authorFactory.createLocalAuthor(formatVersion,
+				authorName, authorPublic, privateKey.getEncoded());
 		// Create a maximum-length forum post
 		GroupId groupId = new GroupId(getRandomId());
 		long timestamp = Long.MAX_VALUE;
 		MessageId parent = new MessageId(getRandomId());
-		String body = StringUtils.getRandomString(MAX_FORUM_POST_BODY_LENGTH);
+		String body = getRandomString(MAX_FORUM_POST_BODY_LENGTH);
 		ForumPost post = forumPostFactory.createPost(groupId,
 				timestamp, parent, author, body);
 		// Check the size of the serialised message
 		int length = post.getMessage().getRaw().length;
-		assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH
+		assertTrue(length > UniqueId.LENGTH + 8 + UniqueId.LENGTH + 4
 				+ MAX_AUTHOR_NAME_LENGTH + MAX_PUBLIC_KEY_LENGTH
-				+ ForumConstants.MAX_CONTENT_TYPE_LENGTH
 				+ MAX_FORUM_POST_BODY_LENGTH);
 		assertTrue(length <= MAX_RECORD_PAYLOAD_LENGTH);
 	}
diff --git a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java
index b12b951cf2..8967961cee 100644
--- a/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/messaging/SimplexMessagingIntegrationTest.java
@@ -5,8 +5,6 @@ import org.briarproject.bramble.api.contact.ContactManager;
 import org.briarproject.bramble.api.crypto.SecretKey;
 import org.briarproject.bramble.api.event.Event;
 import org.briarproject.bramble.api.event.EventListener;
-import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
@@ -36,10 +34,12 @@ import java.io.File;
 import java.io.InputStream;
 import java.io.OutputStream;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
 import static org.briarproject.bramble.test.TestPluginConfigModule.MAX_LATENCY;
 import static org.briarproject.bramble.test.TestPluginConfigModule.TRANSPORT_ID;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
+import static org.briarproject.bramble.test.TestUtils.getTestDirectory;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -47,16 +47,13 @@ import static org.junit.Assert.assertTrue;
 
 public class SimplexMessagingIntegrationTest extends BriarTestCase {
 
-	private final static String ALICE = "Alice";
-	private final static String BOB = "Bob";
-
-	private final File testDir = TestUtils.getTestDirectory();
+	private final File testDir = getTestDirectory();
 	private final File aliceDir = new File(testDir, "alice");
 	private final File bobDir = new File(testDir, "bob");
-	private final SecretKey master = TestUtils.getSecretKey();
+	private final SecretKey master = getSecretKey();
 	private final long timestamp = System.currentTimeMillis();
-	private final AuthorId aliceId = new AuthorId(TestUtils.getRandomId());
-	private final AuthorId bobId = new AuthorId(TestUtils.getRandomId());
+	private final LocalAuthor aliceAuthor = getLocalAuthor();
+	private final LocalAuthor bobAuthor = getLocalAuthor();
 
 	private SimplexMessagingIntegrationTestComponent alice, bob;
 
@@ -95,12 +92,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		lifecycleManager.startServices(null);
 		lifecycleManager.waitForStartup();
 		// Add an identity for Alice
-		LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
 		identityManager.registerLocalAuthor(aliceAuthor);
 		// Add Bob as a contact
-		Author bobAuthor = new Author(bobId, BOB,
-				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = contactManager.addContact(bobAuthor,
 				aliceAuthor.getId(), master, timestamp, true, true, true);
 
@@ -146,12 +139,8 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
 		lifecycleManager.startServices(null);
 		lifecycleManager.waitForStartup();
 		// Add an identity for Bob
-		LocalAuthor bobAuthor = new LocalAuthor(bobId, BOB,
-				new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp);
 		identityManager.registerLocalAuthor(bobAuthor);
 		// Add Alice as a contact
-		Author aliceAuthor = new Author(aliceId, ALICE,
-				new byte[MAX_PUBLIC_KEY_LENGTH]);
 		ContactId contactId = contactManager.addContact(aliceAuthor,
 				bobAuthor.getId(), master, timestamp, false, true, true);
 		// Set up an event listener
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java
index 03ea9b1ba1..d67b74de80 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/GroupMessageValidatorTest.java
@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.sync.InvalidMessageException;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.ValidatorTestCase;
@@ -21,9 +20,8 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.util.StringUtils.getRandomString;
@@ -36,6 +34,7 @@ import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_
 import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
 import static org.briarproject.briar.api.privategroup.invitation.GroupInvitationFactory.SIGNING_LABEL_INVITE;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_INITIAL_JOIN_MSG;
+import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_FORMAT_VERSION;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_ID;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_NAME;
 import static org.briarproject.briar.privategroup.GroupConstants.KEY_MEMBER_PUBLIC_KEY;
@@ -56,119 +55,129 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 	private final GroupInvitationFactory groupInvitationFactory =
 			context.mock(GroupInvitationFactory.class);
 
-	private final String creatorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
-	private final String memberName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
-	private final byte[] creatorKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-	private final byte[] memberKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
+	private final Author member = getAuthor();
+	private final BdfList memberList = BdfList.of(
+			member.getFormatVersion(),
+			member.getName(),
+			member.getPublicKey()
+	);
+	private final byte[] memberSignature = getRandomBytes(MAX_SIGNATURE_LENGTH);
+	private final Author creator = getAuthor();
+	private final BdfList creatorList = BdfList.of(
+			creator.getFormatVersion(),
+			creator.getName(),
+			creator.getPublicKey()
+	);
 	private final byte[] creatorSignature =
 			getRandomBytes(MAX_SIGNATURE_LENGTH);
-	private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
-	private final Author member =
-			new Author(new AuthorId(getRandomId()), memberName, memberKey);
-	private final Author creator =
-			new Author(new AuthorId(getRandomId()), creatorName, creatorKey);
-	private final long inviteTimestamp = 42L;
+	private final long inviteTimestamp = message.getTimestamp() - 1;
+	private final BdfList invite =
+			BdfList.of(inviteTimestamp, creatorSignature);
 	private final PrivateGroup privateGroup = new PrivateGroup(group,
 			getRandomString(MAX_GROUP_NAME_LENGTH), creator,
 			getRandomBytes(GROUP_SALT_LENGTH));
-	private final BdfList token = BdfList.of("token");
+	private final BdfList token = new BdfList();
 	private final MessageId parentId = new MessageId(getRandomId());
 	private final MessageId previousMsgId = new MessageId(getRandomId());
-	private final String postContent =
-			getRandomString(MAX_GROUP_POST_BODY_LENGTH);
+	private final String content = getRandomString(MAX_GROUP_POST_BODY_LENGTH);
 
 	private final GroupMessageValidator validator =
 			new GroupMessageValidator(privateGroupFactory, clientHelper,
-					metadataEncoder, clock, authorFactory,
-					groupInvitationFactory);
+					metadataEncoder, clock, groupInvitationFactory);
 
 	// JOIN message
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortJoinMessage() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, null);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongJoinMessage() throws Exception {
-		expectCreateAuthor(creator);
-		BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey, null,
-				signature, "");
+		expectParseAuthor(memberList, member);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				memberSignature, "");
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithTooShortMemberName() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), "", memberKey, null,
-				signature);
+	public void testRejectsJoinWithNullAuthor() throws Exception {
+		BdfList body = BdfList.of(JOIN.getInt(), null, invite, memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinMessageWithTooLongMemberName() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(),
-				getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), memberKey, null,
-				signature);
+	public void testRejectsJoinWithNonListAuthor() throws Exception {
+		BdfList body = BdfList.of(JOIN.getInt(), 123, invite, memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinMessageWithNullMemberName() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), null, memberKey, null,
-				signature);
+	public void testRejectsJoinWithInvalidAuthor() throws Exception {
+		expectRejectAuthor(memberList);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinMessageWithNonStringMemberName() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), getRandomBytes(5), memberKey,
-				null, signature);
+	public void testRejectsJoinWithNonListInvitation() throws Exception {
+		expectParseAuthor(memberList, member);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, "not a list",
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithTooShortMemberKey() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, new byte[0], null,
-				signature);
+	public void testRejectsCreatorJoinWithTooShortMemberSignature()
+			throws Exception {
+		expectParseAuthor(creatorList, creator);
+		BdfList body = BdfList.of(JOIN.getInt(), creatorList, invite,
+				new byte[0]);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithTooLongMemberKey() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), memberName,
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), null, signature);
+	public void testRejectsCreatorJoinWithTooLongMemberSignature()
+			throws Exception {
+		expectParseAuthor(creatorList, creator);
+		BdfList body = BdfList.of(JOIN.getInt(), creatorList, invite,
+				getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithNoullMemberKey() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, null, null,
-				signature);
+	public void testRejectsCreatorJoinWithNullMemberSignature()
+			throws Exception {
+		expectParseAuthor(creatorList, creator);
+		BdfList body = BdfList.of(JOIN.getInt(), creatorList, invite, null);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithNonRawMemberKey() throws Exception {
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, "not raw", null,
-				signature);
+	public void testRejectsCreatorJoinWithNonRawMemberSignature()
+			throws Exception {
+		expectParseAuthor(creatorList, creator);
+		BdfList body = BdfList.of(JOIN.getInt(), creatorList, invite,
+				"not raw");
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithNonListInvitation() throws Exception {
-		expectCreateAuthor(creator);
-		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey,
-				"not a list", signature);
+	public void testRejectsCreatorJoinWithInvalidMemberSignature()
+			throws Exception {
+		expectCreatorJoinMessage(false);
+		BdfList body = BdfList.of(JOIN.getInt(), creatorList, null,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test
 	public void testAcceptsCreatorJoin() throws Exception {
-		expectJoinMessage(creator, null, true, true);
-		BdfList body = BdfList.of(JOIN.getInt(), creatorName, creatorKey,
-				null, signature);
+		expectCreatorJoinMessage(true);
+		BdfList body = BdfList.of(JOIN.getInt(), creatorList, null,
+				memberSignature);
 		BdfMessageContext messageContext =
 				validator.validateMessage(message, group, body);
 		assertExpectedMessageContext(messageContext, JOIN, creator,
@@ -177,151 +186,183 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 				.getBoolean(KEY_INITIAL_JOIN_MSG));
 	}
 
-	@Test(expected = InvalidMessageException.class)
+	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithNullInvitation() throws Exception {
-		expectCreateAuthor(member);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, null,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, null,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithTooShortInvitation() throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp);
-		expectCreateAuthor(member);
+		BdfList invalidInvite = BdfList.of(inviteTimestamp);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithTooLongInvitation() throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, creatorSignature, "");
-		expectCreateAuthor(member);
+		BdfList invalidInvite =
+				BdfList.of(inviteTimestamp, creatorSignature, "");
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
-	@Test(expected = InvalidMessageException.class)
+	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithEqualInvitationTime()
 			throws Exception {
-		BdfList invite = BdfList.of(message.getTimestamp(), creatorSignature);
-		expectCreateAuthor(member);
+		BdfList invalidInvite =
+				BdfList.of(message.getTimestamp(), creatorSignature);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
-	@Test(expected = InvalidMessageException.class)
+	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithLaterInvitationTime()
 			throws Exception {
-		BdfList invite = BdfList.of(message.getTimestamp() + 1,
-				creatorSignature);
-		expectCreateAuthor(member);
+		BdfList invalidInvite =
+				BdfList.of(message.getTimestamp() + 1, creatorSignature);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsMemberJoinWithNullInvitationTime()
-			throws Exception {
-		BdfList invite = BdfList.of(null, creatorSignature);
-		expectCreateAuthor(member);
+	public void testRejectsMemberJoinWithNullInvitationTime() throws Exception {
+		BdfList invalidInvite = BdfList.of(null, creatorSignature);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithNonLongInvitationTime()
 			throws Exception {
-		BdfList invite = BdfList.of("not long", creatorSignature);
-		expectCreateAuthor(member);
+		BdfList invalidInvite = BdfList.of("not long", creatorSignature);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithTooShortCreatorSignature()
 			throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, new byte[0]);
-		expectCreateAuthor(member);
+		BdfList invalidInvite = BdfList.of(inviteTimestamp, new byte[0]);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsJoinWithTooLongCreatorSignature()
+	public void testRejectsMemberJoinWithTooLongCreatorSignature()
 			throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp,
+		BdfList invalidInvite = BdfList.of(inviteTimestamp,
 				getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
-		expectCreateAuthor(member);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithNullCreatorSignature()
 			throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, null);
-		expectCreateAuthor(member);
+		BdfList invalidInvite = BdfList.of(inviteTimestamp, null);
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithNonRawCreatorSignature()
 			throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, "not raw");
-		expectCreateAuthor(member);
+		BdfList invalidInvite = BdfList.of(inviteTimestamp, "not raw");
+		expectParseAuthor(memberList, member);
 		expectParsePrivateGroup();
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invalidInvite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
-	@Test(expected = InvalidMessageException.class)
+	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithInvalidCreatorSignature()
 			throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, creatorSignature);
-		expectJoinMessage(member, invite, false, true);
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		expectMemberJoinMessage(false, true);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
-	@Test(expected = InvalidMessageException.class)
+	@Test(expected = FormatException.class)
+	public void testRejectsMemberJoinWithTooShortMemberSignature()
+			throws Exception {
+		expectParseAuthor(memberList, member);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				new byte[0]);
+		validator.validateMessage(message, group, body);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsMemberJoinWithTooLongMemberSignature()
+			throws Exception {
+		expectParseAuthor(memberList, member);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
+		validator.validateMessage(message, group, body);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsMemberJoinWithNullMemberSignature()
+			throws Exception {
+		expectParseAuthor(memberList, member);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite, null);
+		validator.validateMessage(message, group, body);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsMemberJoinWithNonRawMemberSignature()
+			throws Exception {
+		expectParseAuthor(memberList, member);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite, "not raw");
+		validator.validateMessage(message, group, body);
+	}
+
+	@Test(expected = FormatException.class)
 	public void testRejectsMemberJoinWithInvalidMemberSignature()
 			throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, creatorSignature);
-		expectJoinMessage(member, invite, true, false);
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		expectMemberJoinMessage(true, false);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test
 	public void testAcceptsMemberJoin() throws Exception {
-		BdfList invite = BdfList.of(inviteTimestamp, creatorSignature);
-		expectJoinMessage(member, invite, true, true);
-		BdfList body = BdfList.of(JOIN.getInt(), memberName, memberKey, invite,
-				signature);
+		expectMemberJoinMessage(true, true);
+		BdfList body = BdfList.of(JOIN.getInt(), memberList, invite,
+				memberSignature);
 		BdfMessageContext messageContext =
 				validator.validateMessage(message, group, body);
 		assertExpectedMessageContext(messageContext, JOIN, member,
@@ -330,11 +371,18 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 				.getBoolean(KEY_INITIAL_JOIN_MSG));
 	}
 
-	private void expectCreateAuthor(Author member) {
+	private void expectParseAuthor(BdfList authorList, Author author)
+			throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(clientHelper).parseAndValidateAuthor(authorList);
+			will(returnValue(author));
+		}});
+	}
+
+	private void expectRejectAuthor(BdfList authorList) throws Exception {
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(member.getName(),
-					member.getPublicKey());
-			will(returnValue(member));
+			oneOf(clientHelper).parseAndValidateAuthor(authorList);
+			will(throwException(new FormatException()));
 		}});
 	}
 
@@ -345,26 +393,46 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 		}});
 	}
 
-	private void expectJoinMessage(Author member, BdfList invite,
-			boolean creatorSigValid, boolean memberSigValid) throws Exception {
-		BdfList signed = BdfList.of(group.getId(), message.getTimestamp(),
-				JOIN.getInt(), member.getName(), member.getPublicKey(), invite);
-		expectCreateAuthor(member);
+	private void expectCreatorJoinMessage(boolean memberSigValid)
+			throws Exception {
+		BdfList signed = BdfList.of(
+				group.getId(),
+				message.getTimestamp(),
+				creatorList,
+				null
+		);
+		expectParseAuthor(creatorList, creator);
 		expectParsePrivateGroup();
 		context.checking(new Expectations() {{
-			if (invite != null) {
-				oneOf(groupInvitationFactory).createInviteToken(creator.getId(),
-						member.getId(), privateGroup.getId(), inviteTimestamp);
-				will(returnValue(token));
-				oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE,
-						creatorSignature, creatorKey, token);
-				if (!memberSigValid)
-					will(throwException(new GeneralSecurityException()));
-			}
-			if (memberSigValid) {
+			oneOf(clientHelper).verifySignature(SIGNING_LABEL_JOIN,
+					memberSignature, creator.getPublicKey(), signed);
+			if (!memberSigValid)
+				will(throwException(new GeneralSecurityException()));
+		}});
+	}
+
+	private void expectMemberJoinMessage(boolean creatorSigValid,
+			boolean memberSigValid) throws Exception {
+		BdfList signed = BdfList.of(
+				group.getId(),
+				message.getTimestamp(),
+				memberList,
+				invite
+		);
+		expectParseAuthor(memberList, member);
+		expectParsePrivateGroup();
+		context.checking(new Expectations() {{
+			oneOf(groupInvitationFactory).createInviteToken(creator.getId(),
+					member.getId(), privateGroup.getId(), inviteTimestamp);
+			will(returnValue(token));
+			oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE,
+					creatorSignature, creator.getPublicKey(), token);
+			if (!creatorSigValid) {
+				will(throwException(new GeneralSecurityException()));
+			} else {
 				oneOf(clientHelper).verifySignature(SIGNING_LABEL_JOIN,
-						signature, member.getPublicKey(), signed);
-				if (!creatorSigValid)
+						memberSignature, member.getPublicKey(), signed);
+				if (!memberSigValid)
 					will(throwException(new GeneralSecurityException()));
 			}
 		}});
@@ -374,216 +442,178 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortPost() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongPost() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent, signature, "");
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsPostWithTooShortMemberName() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), "", memberKey, parentId,
-				previousMsgId, postContent, signature);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, memberSignature, "");
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsPostWithTooLongMemberName() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(),
-				getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), memberKey,
-				parentId, previousMsgId, postContent, signature);
+	public void testRejectsPostWithNullAuthor() throws Exception {
+		BdfList body = BdfList.of(POST.getInt(), null, parentId, previousMsgId,
+				content, memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsPostWithNullMemberName() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), null, memberKey,
-				parentId, previousMsgId, postContent, signature);
+	public void testRejectsPostWithNonListAuthor() throws Exception {
+		BdfList body = BdfList.of(POST.getInt(), 123, parentId, previousMsgId,
+				content, memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsPostWithNonStringMemberName() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), getRandomBytes(5), memberKey,
-				parentId, previousMsgId, postContent, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsPostWithTooShortMemberKey() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, new byte[0],
-				parentId, previousMsgId, postContent, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsPostWithTooLongMemberKey() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName,
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), parentId,
-				previousMsgId, postContent, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsPostWithNullMemberKey() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, null,
-				parentId, previousMsgId, postContent, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsPostWithNonRawMemberKey() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, "not raw",
-				parentId, previousMsgId, postContent, signature);
+	public void testRejectsPostWithInvalidAuthor() throws Exception {
+		expectRejectAuthor(memberList);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, memberSignature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooShortParentId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				getRandomBytes(MessageId.LENGTH - 1), previousMsgId,
-				postContent, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList,
+				getRandomBytes(MessageId.LENGTH - 1), previousMsgId, content,
+				memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooLongParentId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				getRandomBytes(MessageId.LENGTH + 1), previousMsgId,
-				postContent, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList,
+				getRandomBytes(MessageId.LENGTH + 1), previousMsgId, content,
+				memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNonRawParentId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				"not raw", previousMsgId, postContent, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, "not raw",
+				previousMsgId, content, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooShortPreviousMsgId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, getRandomBytes(MessageId.LENGTH - 1), postContent,
-				signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				getRandomBytes(MessageId.LENGTH - 1), content, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooLongPreviousMsgId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, getRandomBytes(MessageId.LENGTH + 1), postContent,
-				signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				getRandomBytes(MessageId.LENGTH + 1), content, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNullPreviousMsgId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, null, postContent, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId, null,
+				content, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNonRawPreviousMsgId() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, "not raw", postContent, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				"not raw", content, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooShortContent() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, "", signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, "", memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooLongContent() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId,
-				getRandomString(MAX_GROUP_POST_BODY_LENGTH + 1), signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, getRandomString(MAX_GROUP_POST_BODY_LENGTH + 1),
+				memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNullContent() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, null, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, null, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNonStringContent() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, getRandomBytes(5), signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, getRandomBytes(5), memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooShortSignature() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent, new byte[0]);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, new byte[0]);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithTooLongSignature() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent,
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content,
 				getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
-		expectCreateAuthor(member);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNullSignature() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent,null);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, null);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsPostWithNonRawSignature() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent, "not raw");
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, "not raw");
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 
-	@Test(expected = InvalidMessageException.class)
+	@Test(expected = FormatException.class)
 	public void testRejectsPostWithInvalidSignature() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent, signature);
-		expectPostMessage(member, parentId, false);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, memberSignature);
+		expectPostMessage(parentId, false);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test
 	public void testAcceptsPost() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey,
-				parentId, previousMsgId, postContent, signature);
-		expectPostMessage(member, parentId, true);
+		BdfList body = BdfList.of(POST.getInt(), memberList, parentId,
+				previousMsgId, content, memberSignature);
+		expectPostMessage(parentId, true);
 		BdfMessageContext messageContext =
 				validator.validateMessage(message, group, body);
 		assertExpectedMessageContext(messageContext, POST, member,
@@ -596,9 +626,9 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 
 	@Test
 	public void testAcceptsTopLevelPost() throws Exception {
-		BdfList body = BdfList.of(POST.getInt(), memberName, memberKey, null,
-				previousMsgId, postContent, signature);
-		expectPostMessage(member, null, true);
+		BdfList body = BdfList.of(POST.getInt(), memberList, null,
+				previousMsgId, content, memberSignature);
+		expectPostMessage(null, true);
 		BdfMessageContext messageContext =
 				validator.validateMessage(message, group, body);
 		assertExpectedMessageContext(messageContext, POST, member,
@@ -609,17 +639,22 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 				messageContext.getDictionary().containsKey(KEY_PARENT_MSG_ID));
 	}
 
-	private void expectPostMessage(Author member, MessageId parentId,
-			boolean sigValid) throws Exception {
-		BdfList signed = BdfList.of(group.getId(), message.getTimestamp(),
-				POST.getInt(), member.getName(), member.getPublicKey(),
+	private void expectPostMessage(MessageId parentId, boolean sigValid)
+			throws Exception {
+		BdfList signed = BdfList.of(
+				group.getId(),
+				message.getTimestamp(),
+				memberList,
 				parentId == null ? null : parentId.getBytes(),
-				previousMsgId.getBytes(), postContent);
-		expectCreateAuthor(member);
+				previousMsgId.getBytes(),
+				content
+		);
+		expectParseAuthor(memberList, member);
 		context.checking(new Expectations() {{
-			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST, signature,
-					member.getPublicKey(), signed);
-			if (!sigValid) will(throwException(new GeneralSecurityException()));
+			oneOf(clientHelper).verifySignature(SIGNING_LABEL_POST,
+					memberSignature, member.getPublicKey(), signed);
+			if (!sigValid)
+				will(throwException(new GeneralSecurityException()));
 		}});
 	}
 
@@ -632,6 +667,8 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 				d.getLong(KEY_TIMESTAMP).longValue());
 		assertFalse(d.getBoolean(KEY_READ));
 		assertEquals(member.getId().getBytes(), d.getRaw(KEY_MEMBER_ID));
+		assertEquals(member.getFormatVersion(),
+				d.getLong(KEY_MEMBER_FORMAT_VERSION).intValue());
 		assertEquals(member.getName(), d.getString(KEY_MEMBER_NAME));
 		assertEquals(member.getPublicKey(), d.getRaw(KEY_MEMBER_PUBLIC_KEY));
 		assertEquals(dependencies, c.getDependencies());
@@ -639,9 +676,9 @@ public class GroupMessageValidatorTest extends ValidatorTestCase {
 
 	@Test(expected = InvalidMessageException.class)
 	public void testRejectsMessageWithUnknownType() throws Exception {
-		BdfList body = BdfList.of(POST.getInt() + 1, memberName, memberKey,
-				parentId, previousMsgId, postContent, signature);
-		expectCreateAuthor(member);
+		BdfList body = BdfList.of(POST.getInt() + 1, memberList,
+				parentId, previousMsgId, content, memberSignature);
+		expectParseAuthor(memberList, member);
 		validator.validateMessage(message, group, body);
 	}
 }
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java
index b1c3fba815..3cab16c48b 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/AbstractProtocolEngineTest.java
@@ -23,8 +23,14 @@ import org.briarproject.briar.api.privategroup.PrivateGroupFactory;
 import org.briarproject.briar.api.privategroup.PrivateGroupManager;
 import org.jmock.Expectations;
 
+import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.GROUP_SALT_LENGTH;
+import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
+import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_NAME_LENGTH;
 import static org.briarproject.briar.api.privategroup.PrivateGroupManager.CLIENT_ID;
 import static org.briarproject.briar.privategroup.invitation.GroupInvitationConstants.GROUP_KEY_CONTACT_ID;
 import static org.briarproject.briar.privategroup.invitation.MessageType.ABORT;
@@ -59,14 +65,13 @@ public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
 	protected final GroupId contactGroupId = new GroupId(getRandomId());
 	protected final GroupId privateGroupId = new GroupId(getRandomId());
 	protected final Group privateGroupGroup =
-			new Group(privateGroupId, CLIENT_ID, getRandomBytes(5));
-	private final AuthorId authorId = new AuthorId(getRandomId());
-	protected final Author author =
-			new Author(authorId, "Author", getRandomBytes(12));
+			new Group(privateGroupId, CLIENT_ID, getRandomBytes(123));
+	protected final Author author = getAuthor();
 	protected final PrivateGroup privateGroup =
-			new PrivateGroup(privateGroupGroup, "Private Group", author,
-					getRandomBytes(8));
-	protected final byte[] signature = getRandomBytes(42);
+			new PrivateGroup(privateGroupGroup,
+					getRandomString(MAX_GROUP_NAME_LENGTH), author,
+					getRandomBytes(GROUP_SALT_LENGTH));
+	protected final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
 	protected final MessageId lastLocalMessageId = new MessageId(getRandomId());
 	protected final MessageId lastRemoteMessageId =
 			new MessageId(getRandomId());
@@ -74,20 +79,19 @@ public abstract class AbstractProtocolEngineTest extends BrambleMockTestCase {
 	protected final long inviteTimestamp = 6L;
 	protected final long messageTimestamp = inviteTimestamp + 1;
 	protected final MessageId messageId = new MessageId(getRandomId());
-	protected final Message message =
-			new Message(messageId, contactGroupId, messageTimestamp,
-					getRandomBytes(42));
+	protected final Message message = new Message(messageId, contactGroupId,
+			messageTimestamp, getRandomBytes(42));
 	private final BdfDictionary meta =
 			BdfDictionary.of(new BdfEntry("me", "ta"));
 	protected final ContactId contactId = new ContactId(5);
-	protected final Contact contact =
-			new Contact(contactId, author, new AuthorId(getRandomId()), true,
-					true);
+	protected final Contact contact = new Contact(contactId, author,
+			new AuthorId(getRandomId()), true, true);
 
 	protected final InviteMessage inviteMessage =
 			new InviteMessage(new MessageId(getRandomId()), contactGroupId,
 					privateGroupId, 0L, privateGroup.getName(),
-					privateGroup.getCreator(), privateGroup.getSalt(), "msg",
+					privateGroup.getCreator(), privateGroup.getSalt(),
+					getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH),
 					signature);
 	protected final JoinMessage joinMessage =
 			new JoinMessage(new MessageId(getRandomId()), contactGroupId,
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidatorTest.java
index 136f34075c..d469b959ed 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/GroupInvitationValidatorTest.java
@@ -4,10 +4,8 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.UniqueId;
 import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.ValidatorTestCase;
@@ -18,9 +16,8 @@ import org.junit.Test;
 
 import java.security.GeneralSecurityException;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.util.StringUtils.getRandomString;
@@ -42,194 +39,149 @@ public class GroupInvitationValidatorTest extends ValidatorTestCase {
 	private final MessageEncoder messageEncoder =
 			context.mock(MessageEncoder.class);
 
+	private final Author creator = getAuthor();
+	private final BdfList creatorList = BdfList.of(
+			creator.getFormatVersion(),
+			creator.getName(),
+			creator.getPublicKey()
+	);
 	private final String groupName = getRandomString(MAX_GROUP_NAME_LENGTH);
-	private final String creatorName = getRandomString(MAX_AUTHOR_NAME_LENGTH);
-	private final byte[] creatorKey = getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-	private final Author creator =
-			new Author(new AuthorId(getRandomId()), creatorName, creatorKey);
 	private final byte[] salt = getRandomBytes(GROUP_SALT_LENGTH);
-	private final PrivateGroup privateGroup =
-			new PrivateGroup(group, groupName, creator, salt);
-	private final String inviteText =
+	private final String content =
 			getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH);
 	private final byte[] signature = getRandomBytes(MAX_SIGNATURE_LENGTH);
-	private final BdfDictionary meta =
-			BdfDictionary.of(new BdfEntry("meta", "data"));
+	private final PrivateGroup privateGroup =
+			new PrivateGroup(group, groupName, creator, salt);
+	private final BdfDictionary meta = new BdfDictionary();
 	private final MessageId previousMessageId = new MessageId(getRandomId());
 
 	private final GroupInvitationValidator validator =
 			new GroupInvitationValidator(clientHelper, metadataEncoder,
-					clock, authorFactory, privateGroupFactory, messageEncoder);
+					clock, privateGroupFactory, messageEncoder);
 
 	// INVITE Message
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortInviteMessage() throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongInviteMessage() throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText, signature, "");
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, signature, "");
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooShortGroupName()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), "", creatorName,
-				creatorKey, salt, inviteText, signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, "", salt,
+				content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooLongGroupName()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(),
-				getRandomString(MAX_GROUP_NAME_LENGTH + 1), creatorName,
-				creatorKey, salt, inviteText, signature);
+		String tooLongName = getRandomString(MAX_GROUP_NAME_LENGTH + 1);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, tooLongName,
+				salt, content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNullGroupName()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), null, creatorName,
-				creatorKey, salt, inviteText, signature);
+	public void testRejectsInviteMessageWithNullGroupName() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, null, salt,
+				content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithNonStringGroupName()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), getRandomBytes(5),
-				creatorName, creatorKey, salt, inviteText, signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList,
+				getRandomBytes(5), salt, content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithTooShortCreatorName()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, "", creatorKey,
-				salt, inviteText, signature);
+	public void testRejectsInviteMessageWithNullCreator() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), null, groupName, salt,
+				content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithTooLongCreatorName()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName,
-				getRandomString(MAX_AUTHOR_NAME_LENGTH + 1), creatorKey, salt,
-				inviteText, signature);
+	public void testRejectsInviteMessageWithNonListCreator() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), 123, groupName, salt,
+				content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNullCreatorName()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, null,
-				creatorKey, salt, inviteText, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNonStringCreatorName()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName,
-				getRandomBytes(5), creatorKey, salt, inviteText, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithTooShortCreatorKey()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				new byte[0], salt, inviteText, signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithTooLongCreatorKey()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1), salt, inviteText,
-				signature);
-		validator.validateMessage(message, group, body);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNullCreatorKey()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				null, salt, inviteText, signature);
-		validator.validateMessage(message, group, body);
-	}
+	public void testRejectsInviteMessageWithInvalidCreator() throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(clientHelper).parseAndValidateAuthor(creatorList);
+			will(throwException(new FormatException()));
+		}});
 
-	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNonRawCreatorKey()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				"not raw", salt, inviteText, signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooShortGroupSalt()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, getRandomBytes(GROUP_SALT_LENGTH - 1), inviteText,
-				signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				getRandomBytes(GROUP_SALT_LENGTH - 1), content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooLongGroupSalt()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, getRandomBytes(GROUP_SALT_LENGTH + 1), inviteText,
-				signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				getRandomBytes(GROUP_SALT_LENGTH + 1), content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNullGroupSalt()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, null, inviteText, signature);
+	public void testRejectsInviteMessageWithNullGroupSalt() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				null, content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNonRawGroupSalt()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, "not raw", inviteText, signature);
+	public void testRejectsInviteMessageWithNonRawGroupSalt() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				"not raw", content, signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooShortContent() throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, "", signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, "", signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooLongContent() throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt,
-				getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH + 1),
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH + 1),
 				signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test
 	public void testAcceptsInviteMessageWithNullContent() throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, null, signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, null, signature);
 		expectInviteMessage(false);
 		validator.validateMessage(message, group, body);
 	}
@@ -237,57 +189,54 @@ public class GroupInvitationValidatorTest extends ValidatorTestCase {
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithNonStringContent()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, getRandomBytes(5), signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, getRandomBytes(5), signature);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooShortSignature()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText, new byte[0]);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, new byte[0]);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithTooLongSignature()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText,
-				getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, getRandomBytes(MAX_SIGNATURE_LENGTH + 1));
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNullSignature()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText, null);
+	public void testRejectsInviteMessageWithNullSignature() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, null);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsInviteMessageWithNonRawSignature()
-			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText, "not raw");
+	public void testRejectsInviteMessageWithNonRawSignature() throws Exception {
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, "not raw");
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInviteMessageWithInvalidSignature()
 			throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, null, signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, signature);
 		expectInviteMessage(true);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test
 	public void testAcceptsValidInviteMessage() throws Exception {
-		BdfList body = BdfList.of(INVITE.getValue(), groupName, creatorName,
-				creatorKey, salt, inviteText, signature);
+		BdfList body = BdfList.of(INVITE.getValue(), creatorList, groupName,
+				salt, content, signature);
 		expectInviteMessage(false);
 		BdfMessageContext messageContext =
 				validator.validateMessage(message, group, body);
@@ -296,16 +245,19 @@ public class GroupInvitationValidatorTest extends ValidatorTestCase {
 	}
 
 	private void expectInviteMessage(boolean exception) throws Exception {
-		BdfList signed = BdfList.of(message.getTimestamp(),
-				message.getGroupId(), privateGroup.getId());
+		BdfList signed = BdfList.of(
+				message.getTimestamp(),
+				message.getGroupId(),
+				privateGroup.getId()
+		);
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(creatorName, creatorKey);
+			oneOf(clientHelper).parseAndValidateAuthor(creatorList);
 			will(returnValue(creator));
 			oneOf(privateGroupFactory).createPrivateGroup(groupName, creator,
 					salt);
 			will(returnValue(privateGroup));
 			oneOf(clientHelper).verifySignature(SIGNING_LABEL_INVITE, signature,
-					creatorKey, signed);
+					creator.getPublicKey(), signed);
 			if (exception) {
 				will(throwException(new GeneralSecurityException()));
 			} else {
diff --git a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
index f2eb56a256..0030945428 100644
--- a/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/privategroup/invitation/InviteeProtocolEngineTest.java
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.contact.Contact;
 import org.briarproject.bramble.api.data.BdfDictionary;
 import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.briar.api.client.ProtocolStateException;
@@ -18,8 +17,11 @@ import java.util.Map;
 import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
 import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
 import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.test.TestUtils.getLocalAuthor;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_INVITATION_MSG_LENGTH;
 import static org.briarproject.briar.privategroup.invitation.InviteeState.ACCEPTED;
 import static org.briarproject.briar.privategroup.invitation.InviteeState.DISSOLVED;
 import static org.briarproject.briar.privategroup.invitation.InviteeState.ERROR;
@@ -39,9 +41,7 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
 			new InviteeProtocolEngine(db, clientHelper, privateGroupManager,
 					privateGroupFactory, groupMessageFactory, identityManager,
 					messageParser, messageEncoder, messageTracker, clock);
-	private final LocalAuthor localAuthor =
-			new LocalAuthor(new AuthorId(getRandomId()), "Local Author",
-					getRandomBytes(12), getRandomBytes(12), 42L);
+	private final LocalAuthor localAuthor = getLocalAuthor();
 
 	private InviteeSession getDefaultSession(InviteeState state) {
 		return new InviteeSession(contactGroupId, privateGroupId,
@@ -323,13 +323,12 @@ public class InviteeProtocolEngineTest extends AbstractProtocolEngineTest {
 				new InviteMessage(new MessageId(getRandomId()), contactGroupId,
 						privateGroupId, session.getInviteTimestamp() + 1,
 						privateGroup.getName(), privateGroup.getCreator(),
-						privateGroup.getSalt(), "msg", signature);
-		Author notCreator =
-				new Author(new AuthorId(getRandomId()), "Not Creator",
-						getRandomBytes(5));
-		Contact notCreatorContact =
-				new Contact(contactId, notCreator, localAuthor.getId(), true,
-						true);
+						privateGroup.getSalt(),
+						getRandomString(MAX_GROUP_INVITATION_MSG_LENGTH),
+						signature);
+		Author notCreator = getAuthor();
+		Contact notCreatorContact = new Contact(contactId, notCreator,
+				localAuthor.getId(), true, true);
 
 		expectGetContactId();
 		context.checking(new Expectations() {{
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java
index 9500d99381..d191fada2c 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingManagerImplTest.java
@@ -83,12 +83,10 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase {
 		MessageParser<Blog> messageParser = context.mock(MessageParser.class);
 		InvitationFactory<Blog, BlogInvitationResponse> invitationFactory =
 				context.mock(InvitationFactory.class);
-		blogSharingManager =
-				new BlogSharingManagerImpl(db, clientHelper, metadataParser,
-						messageParser, sessionEncoder, sessionParser,
-						messageTracker, contactGroupFactory,
-						engine, invitationFactory, identityManager,
-						blogManager);
+		blogSharingManager = new BlogSharingManagerImpl(db, clientHelper,
+				metadataParser, messageParser, sessionEncoder, sessionParser,
+				messageTracker, contactGroupFactory, engine, invitationFactory,
+				identityManager, blogManager);
 	}
 
 	@Test
@@ -228,13 +226,11 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase {
 	private void expectPreShareShareable(Transaction txn, Contact contact,
 			Blog blog, Map<MessageId, BdfDictionary> sessions)
 			throws Exception {
-		Group contactGroup =
-				new Group(new GroupId(getRandomId()), CLIENT_ID,
-						getRandomBytes(42));
+		Group contactGroup = new Group(new GroupId(getRandomId()), CLIENT_ID,
+				getRandomBytes(42));
 		BdfDictionary sessionDict = new BdfDictionary();
-		Message message =
-				new Message(new MessageId(getRandomId()), contactGroup.getId(),
-						42L, getRandomBytes(1337));
+		Message message = new Message(new MessageId(getRandomId()),
+				contactGroup.getId(), 42L, getRandomBytes(1337));
 		context.checking(new Expectations() {{
 			oneOf(contactGroupFactory).createContactGroup(CLIENT_ID,
 					CLIENT_VERSION, contact);
@@ -242,9 +238,8 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase {
 			oneOf(sessionParser)
 					.getSessionQuery(new SessionId(blog.getId().getBytes()));
 			will(returnValue(sessionDict));
-			oneOf(clientHelper)
-					.getMessageMetadataAsDictionary(txn, contactGroup.getId(),
-							sessionDict);
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					contactGroup.getId(), sessionDict);
 			will(returnValue(sessions));
 			if (sessions.size() == 0) {
 				oneOf(db).addGroup(txn, blog.getGroup());
@@ -277,9 +272,8 @@ public class BlogSharingManagerImplTest extends BrambleMockTestCase {
 			oneOf(sessionParser)
 					.getSessionQuery(new SessionId(blog.getId().getBytes()));
 			will(returnValue(sessionDict));
-			oneOf(clientHelper)
-					.getMessageMetadataAsDictionary(txn, contactGroup.getId(),
-							sessionDict);
+			oneOf(clientHelper).getMessageMetadataAsDictionary(txn,
+					contactGroup.getId(), sessionDict);
 			will(returnValue(sessions));
 			if (sessions.size() == 1) {
 				oneOf(sessionParser)
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java
index c363f4330e..9e20390830 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/BlogSharingValidatorTest.java
@@ -4,184 +4,130 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.identity.Author;
-import org.briarproject.bramble.api.identity.AuthorId;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.blog.Blog;
 import org.jmock.Expectations;
 import org.junit.Test;
 
-import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.bramble.test.TestUtils.getRandomId;
-import static org.briarproject.briar.api.blog.BlogConstants.MAX_BLOG_NAME_LENGTH;
+import static org.briarproject.bramble.test.TestUtils.getAuthor;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
 import static org.briarproject.briar.sharing.MessageType.INVITE;
 
 public class BlogSharingValidatorTest extends SharingValidatorTest {
 
-	private final AuthorId authorId = new AuthorId(getRandomId());
-	private final String authorName = StringUtils.getRandomString(42);
-	private final byte[] publicKey =
-			TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH);
-	private final Author author = new Author(authorId, authorName, publicKey);
+	private final Author author = getAuthor();
 	private final Blog blog = new Blog(group, author, false);
-	private final BdfList descriptor = BdfList.of(authorName, publicKey, false);
+	private final BdfList authorList = BdfList.of(author.getFormatVersion(),
+			author.getName(), author.getPublicKey());
+	private final BdfList descriptor = BdfList.of(authorList, false);
 	private final String content =
-			StringUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
+			getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
 
 	@Override
 	SharingValidator getValidator() {
 		return new BlogSharingValidator(messageEncoder, clientHelper,
-				metadataEncoder, clock, blogFactory, authorFactory);
+				metadataEncoder, clock, blogFactory);
 	}
 
 	@Test
 	public void testAcceptsInvitationWithContent() throws Exception {
-		expectCreateBlog(authorName, publicKey);
+		expectCreateBlog();
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
 						content));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsInvitationWithNullContent() throws Exception {
-		expectCreateBlog(authorName, publicKey);
+		expectCreateBlog();
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsInvitationWithNullPreviousMsgId() throws Exception {
-		expectCreateBlog(authorName, publicKey);
+		expectCreateBlog();
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), null, descriptor, null));
-		assertExpectedContext(messageContext, null);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNullBlogName() throws Exception {
-		BdfList invalidDescriptor = BdfList.of(null, publicKey, false);
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
-						null));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsNonStringBlogName() throws Exception {
-		BdfList invalidDescriptor = BdfList.of(123, publicKey, false);
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
-						null));
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooShortBlogName() throws Exception {
-		BdfList invalidDescriptor = BdfList.of("", publicKey, false);
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
-						null));
+		BdfMessageContext context = validator.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), null, descriptor, content));
+		assertExpectedContext(context, null);
 	}
 
 	@Test
-	public void testAcceptsMinLengthBlogName() throws Exception {
-		String shortBlogName = StringUtils.getRandomString(1);
-		BdfList validDescriptor = BdfList.of(shortBlogName, publicKey, false);
-		expectCreateBlog(shortBlogName, publicKey);
+	public void testAcceptsInvitationForRssBlog() throws Exception {
+		expectCreateRssBlog();
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
-						null));
-		assertExpectedContext(messageContext, previousMsgId);
-	}
-
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongBlogName() throws Exception {
-		String invalidBlogName =
-				StringUtils.getRandomString(MAX_BLOG_NAME_LENGTH + 1);
-		BdfList invalidDescriptor =
-				BdfList.of(invalidBlogName, publicKey, false);
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
-						null));
+		BdfList rssDescriptor = BdfList.of(authorList, true);
+		BdfMessageContext context = validator.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, rssDescriptor,
+						content));
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsNullPublicKey() throws Exception {
-		BdfList invalidDescriptor = BdfList.of(authorName, null, false);
-		v.validateMessage(message, group,
+	public void testRejectsNullAuthor() throws Exception {
+		BdfList invalidDescriptor = BdfList.of(null, false);
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
 
 	@Test(expected = FormatException.class)
-	public void testRejectsNonRawPublicKey() throws Exception {
-		BdfList invalidDescriptor = BdfList.of(authorName, 123, false);
-		v.validateMessage(message, group,
+	public void testRejectsNonListAuthor() throws Exception {
+		BdfList invalidDescriptor = BdfList.of(123, false);
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
 
-	@Test(expected = FormatException.class)
-	public void testRejectsTooLongPublicKey() throws Exception {
-		byte[] invalidKey = TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH + 1);
-		BdfList invalidDescriptor = BdfList.of(authorName, invalidKey, false);
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
-						null));
-	}
-
-	@Test
-	public void testAcceptsMinLengthPublicKey() throws Exception {
-		byte[] key = TestUtils.getRandomBytes(1);
-		BdfList validDescriptor = BdfList.of(authorName, key, false);
-
-		expectCreateBlog(authorName, key);
-		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
-						null));
-		assertExpectedContext(messageContext, previousMsgId);
-	}
-
 	@Test(expected = FormatException.class)
 	public void testRejectsNonStringContent() throws Exception {
-		expectCreateBlog(authorName, publicKey);
-		v.validateMessage(message, group,
+		expectCreateBlog();
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
 						123));
 	}
 
 	@Test
 	public void testAcceptsMinLengthContent() throws Exception {
-		expectCreateBlog(authorName, publicKey);
+		String shortContent = getRandomString(1);
+		expectCreateBlog();
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, "1"));
-		assertExpectedContext(messageContext, previousMsgId);
+		BdfMessageContext context = validator.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
+						shortContent));
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongContent() throws Exception {
 		String invalidContent =
-				StringUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
-		expectCreateBlog(authorName, publicKey);
-		v.validateMessage(message, group,
+				getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
+		expectCreateBlog();
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
 						invalidContent));
 	}
 
-	private void expectCreateBlog(String name, byte[] key) {
+	private void expectCreateBlog() throws Exception {
 		context.checking(new Expectations() {{
-			oneOf(authorFactory).createAuthor(name, key);
+			oneOf(clientHelper).parseAndValidateAuthor(authorList);
 			will(returnValue(author));
 			oneOf(blogFactory).createBlog(author);
 			will(returnValue(blog));
 		}});
 	}
 
+	private void expectCreateRssBlog() throws Exception {
+		context.checking(new Expectations() {{
+			oneOf(clientHelper).parseAndValidateAuthor(authorList);
+			will(returnValue(author));
+			oneOf(blogFactory).createFeedBlog(author);
+			will(returnValue(blog));
+		}});
+	}
 }
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
index 34599f4663..6197287742 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/ForumSharingValidatorTest.java
@@ -3,12 +3,12 @@ package org.briarproject.briar.sharing;
 import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.data.BdfList;
-import org.briarproject.bramble.test.TestUtils;
-import org.briarproject.bramble.util.StringUtils;
 import org.briarproject.briar.api.forum.Forum;
 import org.jmock.Expectations;
 import org.junit.Test;
 
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.forum.ForumConstants.FORUM_SALT_LENGTH;
 import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
 import static org.briarproject.briar.api.sharing.SharingConstants.MAX_INVITATION_MESSAGE_LENGTH;
@@ -16,13 +16,12 @@ import static org.briarproject.briar.sharing.MessageType.INVITE;
 
 public class ForumSharingValidatorTest extends SharingValidatorTest {
 
-	private final String forumName =
-			StringUtils.getRandomString(MAX_FORUM_NAME_LENGTH);
-	private final byte[] salt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH);
+	private final String forumName = getRandomString(MAX_FORUM_NAME_LENGTH);
+	private final byte[] salt = getRandomBytes(FORUM_SALT_LENGTH);
 	private final Forum forum = new Forum(group, forumName, salt);
 	private final BdfList descriptor = BdfList.of(forumName, salt);
 	private final String content =
-			StringUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
+			getRandomString(MAX_INVITATION_MESSAGE_LENGTH);
 
 	@Override
 	SharingValidator getValidator() {
@@ -34,34 +33,34 @@ public class ForumSharingValidatorTest extends SharingValidatorTest {
 	public void testAcceptsInvitationWithContent() throws Exception {
 		expectCreateForum(forumName);
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
 						content));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsInvitationWithNullContent() throws Exception {
 		expectCreateForum(forumName);
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsInvitationWithNullPreviousMsgId() throws Exception {
 		expectCreateForum(forumName);
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), null, descriptor, null));
-		assertExpectedContext(messageContext, null);
+		assertExpectedContext(context, null);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNullForumName() throws Exception {
 		BdfList invalidDescriptor = BdfList.of(null, salt);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
@@ -69,7 +68,7 @@ public class ForumSharingValidatorTest extends SharingValidatorTest {
 	@Test(expected = FormatException.class)
 	public void testRejectsNonStringForumName() throws Exception {
 		BdfList invalidDescriptor = BdfList.of(123, salt);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
@@ -77,29 +76,28 @@ public class ForumSharingValidatorTest extends SharingValidatorTest {
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortForumName() throws Exception {
 		BdfList invalidDescriptor = BdfList.of("", salt);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
 
 	@Test
 	public void testAcceptsMinLengthForumName() throws Exception {
-		String shortForumName = StringUtils.getRandomString(1);
+		String shortForumName = getRandomString(1);
 		BdfList validDescriptor = BdfList.of(shortForumName, salt);
 		expectCreateForum(shortForumName);
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, validDescriptor,
 						null));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongForumName() throws Exception {
-		String invalidForumName =
-				StringUtils.getRandomString(MAX_FORUM_NAME_LENGTH + 1);
+		String invalidForumName = getRandomString(MAX_FORUM_NAME_LENGTH + 1);
 		BdfList invalidDescriptor = BdfList.of(invalidForumName, salt);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
@@ -107,7 +105,7 @@ public class ForumSharingValidatorTest extends SharingValidatorTest {
 	@Test(expected = FormatException.class)
 	public void testRejectsNullSalt() throws Exception {
 		BdfList invalidDescriptor = BdfList.of(forumName, null);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
@@ -115,25 +113,25 @@ public class ForumSharingValidatorTest extends SharingValidatorTest {
 	@Test(expected = FormatException.class)
 	public void testRejectsNonRawSalt() throws Exception {
 		BdfList invalidDescriptor = BdfList.of(forumName, 123);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortSalt() throws Exception {
-		byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH - 1);
+		byte[] invalidSalt = getRandomBytes(FORUM_SALT_LENGTH - 1);
 		BdfList invalidDescriptor = BdfList.of(forumName, invalidSalt);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongSalt() throws Exception {
-		byte[] invalidSalt = TestUtils.getRandomBytes(FORUM_SALT_LENGTH + 1);
+		byte[] invalidSalt = getRandomBytes(FORUM_SALT_LENGTH + 1);
 		BdfList invalidDescriptor = BdfList.of(forumName, invalidSalt);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, invalidDescriptor,
 						null));
 	}
@@ -141,26 +139,25 @@ public class ForumSharingValidatorTest extends SharingValidatorTest {
 	@Test(expected = FormatException.class)
 	public void testRejectsNonStringContent() throws Exception {
 		expectCreateForum(forumName);
-		v.validateMessage(message, group,
-				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
-						123));
+		validator.validateMessage(message, group,
+				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, 123));
 	}
 
 	@Test
 	public void testAcceptsMinLengthContent() throws Exception {
 		expectCreateForum(forumName);
 		expectEncodeMetadata(INVITE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, "1"));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongContent() throws Exception {
 		String invalidContent =
-				StringUtils.getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
+				getRandomString(MAX_INVITATION_MESSAGE_LENGTH + 1);
 		expectCreateForum(forumName);
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor,
 						invalidContent));
 	}
diff --git a/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java
index 20a9b95e74..157c4ce79d 100644
--- a/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/sharing/SharingValidatorTest.java
@@ -4,10 +4,8 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.UniqueId;
 import org.briarproject.bramble.api.client.BdfMessageContext;
 import org.briarproject.bramble.api.data.BdfDictionary;
-import org.briarproject.bramble.api.data.BdfEntry;
 import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.sync.MessageId;
-import org.briarproject.bramble.test.TestUtils;
 import org.briarproject.bramble.test.ValidatorTestCase;
 import org.briarproject.briar.api.blog.BlogFactory;
 import org.briarproject.briar.api.forum.ForumFactory;
@@ -18,6 +16,7 @@ import java.util.Collection;
 
 import javax.annotation.Nullable;
 
+import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.briar.sharing.MessageType.ABORT;
 import static org.briarproject.briar.sharing.MessageType.ACCEPT;
@@ -29,28 +28,26 @@ import static org.junit.Assert.assertTrue;
 
 public abstract class SharingValidatorTest extends ValidatorTestCase {
 
-	protected final MessageEncoder messageEncoder =
-			context.mock(MessageEncoder.class);
-	protected final ForumFactory forumFactory =
-			context.mock(ForumFactory.class);
-	protected final BlogFactory blogFactory = context.mock(BlogFactory.class);
-	protected final SharingValidator v = getValidator();
+	final MessageEncoder messageEncoder = context.mock(MessageEncoder.class);
+	final ForumFactory forumFactory = context.mock(ForumFactory.class);
+	final BlogFactory blogFactory = context.mock(BlogFactory.class);
 
-	protected final MessageId previousMsgId = new MessageId(getRandomId());
-	private final BdfDictionary meta =
-			BdfDictionary.of(new BdfEntry("meta", "data"));
+	final SharingValidator validator = getValidator();
+
+	final MessageId previousMsgId = new MessageId(getRandomId());
+	private final BdfDictionary meta = new BdfDictionary();
 
 	abstract SharingValidator getValidator();
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortBodyForInvitation() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongBodyForInvitation() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(INVITE.getValue(), previousMsgId, descriptor, null,
 						123));
 	}
@@ -58,101 +55,101 @@ public abstract class SharingValidatorTest extends ValidatorTestCase {
 	@Test
 	public void testAcceptsAccept() throws Exception {
 		expectEncodeMetadata(ACCEPT);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(ACCEPT.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsDecline() throws Exception {
 		expectEncodeMetadata(DECLINE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(DECLINE.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsLeave() throws Exception {
 		expectEncodeMetadata(LEAVE);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(LEAVE.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test
 	public void testAcceptsAbort() throws Exception {
 		expectEncodeMetadata(ABORT);
-		BdfMessageContext messageContext = v.validateMessage(message, group,
+		BdfMessageContext context = validator.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), groupId, previousMsgId));
-		assertExpectedContext(messageContext, previousMsgId);
+		assertExpectedContext(context, previousMsgId);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNullMessageType() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(null, groupId, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonLongMessageType() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of("", groupId, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInvalidMessageType() throws Exception {
 		int invalidMessageType = ABORT.getValue() + 1;
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(invalidMessageType, groupId, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNullSessionId() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), null, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsNonRawSessionId() throws Exception {
-		v.validateMessage(message, group, BdfList.of(ABORT.getValue(), 123));
+		validator.validateMessage(message, group,
+				BdfList.of(ABORT.getValue(), 123));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortSessionId() throws Exception {
-		byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH - 1);
-		v.validateMessage(message, group,
+		byte[] invalidGroupId = getRandomBytes(UniqueId.LENGTH - 1);
+		validator.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongSessionId() throws Exception {
-		byte[] invalidGroupId = TestUtils.getRandomBytes(UniqueId.LENGTH + 1);
-		v.validateMessage(message, group,
+		byte[] invalidGroupId = getRandomBytes(UniqueId.LENGTH + 1);
+		validator.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), invalidGroupId, previousMsgId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortBodyForAbort() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), groupId));
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongBodyForAbort() throws Exception {
-		v.validateMessage(message, group,
+		validator.validateMessage(message, group,
 				BdfList.of(ABORT.getValue(), groupId, previousMsgId, 123));
 	}
 
-	protected void expectEncodeMetadata(MessageType type) {
+	void expectEncodeMetadata(MessageType type) {
 		context.checking(new Expectations() {{
-			oneOf(messageEncoder)
-					.encodeMetadata(type, groupId, timestamp, false, false,
-							false, false, false);
+			oneOf(messageEncoder).encodeMetadata(type, groupId, timestamp,
+					false, false, false, false, false);
 			will(returnValue(meta));
 		}});
 	}
 
-	protected void assertExpectedContext(BdfMessageContext messageContext,
+	void assertExpectedContext(BdfMessageContext messageContext,
 			@Nullable MessageId previousMsgId) throws FormatException {
 		Collection<MessageId> dependencies = messageContext.getDependencies();
 		if (previousMsgId == null) {
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
index 1376500ddd..3c2138b613 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTest.java
@@ -30,7 +30,6 @@ import org.briarproject.bramble.lifecycle.LifecycleModule;
 import org.briarproject.bramble.properties.PropertiesModule;
 import org.briarproject.bramble.sync.SyncModule;
 import org.briarproject.bramble.system.SystemModule;
-import org.briarproject.bramble.test.TestPluginConfigModule;
 import org.briarproject.bramble.test.TestUtils;
 import org.briarproject.bramble.transport.TransportModule;
 import org.briarproject.briar.api.blog.BlogFactory;
@@ -64,6 +63,7 @@ import static junit.framework.Assert.assertNotNull;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
 import static org.briarproject.bramble.api.sync.ValidationManager.State.PENDING;
+import static org.briarproject.bramble.test.TestPluginConfigModule.MAX_LATENCY;
 import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.junit.Assert.assertTrue;
 
@@ -328,7 +328,7 @@ public abstract class BriarIntegrationTest<C extends BriarIntegrationTestCompone
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		// Create an outgoing sync session
 		SyncSession sessionFrom =
-				fromSync.createSimplexOutgoingSession(toId, TestPluginConfigModule.MAX_LATENCY, out);
+				fromSync.createSimplexOutgoingSession(toId, MAX_LATENCY, out);
 		// Write whatever needs to be written
 		sessionFrom.run();
 		out.close();
-- 
GitLab