diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java
index f05a176cea642b961b0b75ab4092e9cbb57cfa37..5ffe422c2ac7dd369a57ff3673335ddfb88eb25a 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Group.java
@@ -10,6 +10,11 @@ public class Group {
 		SHARED // The group is visible and messages are shared
 	}
 
+	/**
+	 * The current version of the group format.
+	 */
+	public static final int FORMAT_VERSION = 1;
+
 	private final GroupId id;
 	private final ClientId clientId;
 	private final byte[] descriptor;
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Message.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Message.java
index 3d4216270fae5d2d910ba6a2093eb1da4b62cbec..da13796a00904ac15fa2b0e97e26e578170cc95e 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Message.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/Message.java
@@ -5,6 +5,11 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LEN
 
 public class Message {
 
+	/**
+	 * The current version of the message format.
+	 */
+	public static final int FORMAT_VERSION = 1;
+
 	private final MessageId id;
 	private final GroupId groupId;
 	private final long timestamp;
diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java
index 07487eb8c949e4c18561f6988af4130571bff836..907b6e2d08e8b3c8e0501434aabb361bfa4d4afe 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageId.java
@@ -16,7 +16,13 @@ public class MessageId extends UniqueId {
 	/**
 	 * Label for hashing messages to calculate their identifiers.
 	 */
-	public static final String LABEL = "org.briarproject.bramble/MESSAGE_ID";
+	public static final String ID_LABEL = "org.briarproject.bramble/MESSAGE_ID";
+
+	/**
+	 * Label for hashing blocks of messages.
+	 */
+	public static final String BLOCK_LABEL =
+			"org.briarproject.bramble/MESSAGE_BLOCK";
 
 	public MessageId(byte[] id) {
 		super(id);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java
index a28592bdce4338fe51939a39555d3cdcfecdd8a1..e7bc4e16e6dca75ee7622f0d42c21d69a56d02b1 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/GroupFactoryImpl.java
@@ -12,8 +12,8 @@ import org.briarproject.bramble.util.StringUtils;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.bramble.api.sync.Group.FORMAT_VERSION;
 import static org.briarproject.bramble.api.sync.GroupId.LABEL;
-import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
 
 @Immutable
@@ -31,7 +31,7 @@ class GroupFactoryImpl implements GroupFactory {
 	public Group createGroup(ClientId c, int clientVersion, byte[] descriptor) {
 		byte[] clientVersionBytes = new byte[INT_32_BYTES];
 		ByteUtils.writeUint32(clientVersion, clientVersionBytes, 0);
-		byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
+		byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION},
 				StringUtils.toUtf8(c.getString()), clientVersionBytes,
 				descriptor);
 		return new Group(new GroupId(hash), c, descriptor);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java
index e7a68d7d8a50d0328473b72bed38c9b92e341c2d..7f92045fcbbf6984308bc0246ddd529e79d26827 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/MessageFactoryImpl.java
@@ -12,10 +12,12 @@ import org.briarproject.bramble.util.ByteUtils;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
-import static org.briarproject.bramble.api.sync.MessageId.LABEL;
+import static org.briarproject.bramble.api.sync.Message.FORMAT_VERSION;
+import static org.briarproject.bramble.api.sync.MessageId.BLOCK_LABEL;
+import static org.briarproject.bramble.api.sync.MessageId.ID_LABEL;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
+import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
 
 @Immutable
 @NotNullByDefault
@@ -32,11 +34,14 @@ class MessageFactoryImpl implements MessageFactory {
 	public Message createMessage(GroupId g, long timestamp, byte[] body) {
 		if (body.length > MAX_MESSAGE_BODY_LENGTH)
 			throw new IllegalArgumentException();
-		byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
+		byte[] versionBytes = new byte[] {FORMAT_VERSION};
+		// There's only one block, so the root hash is the hash of the block
+		byte[] rootHash = crypto.hash(BLOCK_LABEL, versionBytes, body);
+		byte[] timeBytes = new byte[INT_64_BYTES];
 		ByteUtils.writeUint64(timestamp, timeBytes, 0);
-		byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
-				g.getBytes(), timeBytes, body);
-		MessageId id = new MessageId(hash);
+		byte[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(),
+				timeBytes, rootHash);
+		MessageId id = new MessageId(idHash);
 		byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
 		System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
 		ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java b/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java
index 52ecdb50aabe262d5f06a6c3e70c187f1d1266a7..46ee505dfa0cf6ad68f119ef8563a024b16f9f42 100644
--- a/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java
+++ b/briar-core/src/main/java/org/briarproject/briar/client/BriarClientModule.java
@@ -1,8 +1,8 @@
 package org.briarproject.briar.client;
 
 import org.briarproject.bramble.api.client.ClientHelper;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.db.DatabaseComponent;
+import org.briarproject.bramble.api.sync.MessageFactory;
 import org.briarproject.bramble.api.sync.ValidationManager;
 import org.briarproject.briar.api.client.MessageQueueManager;
 import org.briarproject.briar.api.client.MessageTracker;
@@ -26,8 +26,9 @@ public class BriarClientModule {
 	}
 
 	@Provides
-	QueueMessageFactory provideQueueMessageFactory(CryptoComponent crypto) {
-		return new QueueMessageFactoryImpl(crypto);
+	QueueMessageFactory provideQueueMessageFactory(
+			MessageFactory messageFactory) {
+		return new QueueMessageFactoryImpl(messageFactory);
 	}
 
 	@Provides
diff --git a/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java b/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java
index da8cb1426e996a2daa75c1503afce11c250e6baf..480b7670b8a6d7b56d715a0ef144dea483488621 100644
--- a/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/client/QueueMessageFactoryImpl.java
@@ -1,9 +1,10 @@
 package org.briarproject.briar.client;
 
 import org.briarproject.bramble.api.UniqueId;
-import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.sync.GroupId;
+import org.briarproject.bramble.api.sync.Message;
+import org.briarproject.bramble.api.sync.MessageFactory;
 import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.util.ByteUtils;
 import org.briarproject.briar.api.client.QueueMessage;
@@ -12,10 +13,8 @@ import org.briarproject.briar.api.client.QueueMessageFactory;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
-import static org.briarproject.bramble.api.sync.MessageId.LABEL;
 import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
 import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
 import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
 import static org.briarproject.briar.api.client.QueueMessage.MAX_QUEUE_MESSAGE_BODY_LENGTH;
 import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADER_LENGTH;
@@ -24,11 +23,11 @@ import static org.briarproject.briar.api.client.QueueMessage.QUEUE_MESSAGE_HEADE
 @NotNullByDefault
 class QueueMessageFactoryImpl implements QueueMessageFactory {
 
-	private final CryptoComponent crypto;
+	private final MessageFactory messageFactory;
 
 	@Inject
-	QueueMessageFactoryImpl(CryptoComponent crypto) {
-		this.crypto = crypto;
+	QueueMessageFactoryImpl(MessageFactory messageFactory) {
+		this.messageFactory = messageFactory;
 	}
 
 	@Override
@@ -36,21 +35,13 @@ class QueueMessageFactoryImpl implements QueueMessageFactory {
 			long queuePosition, byte[] body) {
 		if (body.length > MAX_QUEUE_MESSAGE_BODY_LENGTH)
 			throw new IllegalArgumentException();
-		byte[] raw = new byte[QUEUE_MESSAGE_HEADER_LENGTH + body.length];
-		System.arraycopy(groupId.getBytes(), 0, raw, 0, UniqueId.LENGTH);
-		ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
-		ByteUtils.writeUint64(queuePosition, raw, MESSAGE_HEADER_LENGTH);
-		System.arraycopy(body, 0, raw, QUEUE_MESSAGE_HEADER_LENGTH,
-				body.length);
-		byte[] timeBytes = new byte[INT_64_BYTES];
-		ByteUtils.writeUint64(timestamp, timeBytes, 0);
-		byte[] bodyBytes = new byte[body.length + INT_64_BYTES];
-		System.arraycopy(raw, MESSAGE_HEADER_LENGTH, bodyBytes, 0,
-				body.length + INT_64_BYTES);
-		byte[] hash = crypto.hash(LABEL, new byte[] {PROTOCOL_VERSION},
-				groupId.getBytes(), timeBytes, bodyBytes);
-		MessageId id = new MessageId(hash);
-		return new QueueMessage(id, groupId, timestamp, queuePosition, raw);
+		byte[] messageBody = new byte[INT_64_BYTES + body.length];
+		ByteUtils.writeUint64(queuePosition, messageBody, 0);
+		System.arraycopy(body, 0, messageBody, INT_64_BYTES, body.length);
+		Message m = messageFactory.createMessage(groupId, timestamp,
+				messageBody);
+		return new QueueMessage(m.getId(), groupId, timestamp, queuePosition,
+				m.getRaw());
 	}
 
 	@Override