diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java
index e85b15d7df3426298fd5139bf05d12041a4da2b7..a31e7737000a80d1511c5e9e57fa3bb1c5880bcd 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/sync/MessageFactory.java
@@ -7,5 +7,7 @@ public interface MessageFactory {
 
 	Message createMessage(GroupId g, long timestamp, byte[] body);
 
+	Message createMessage(byte[] raw);
+
 	Message createMessage(MessageId m, byte[] raw);
 }
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 5a46a4aad5287d4e896778834681b703ae0336aa..0e34cf6b4db4005ce63088346d7e0b036be42346 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
@@ -20,6 +20,9 @@ import static org.briarproject.bramble.util.ByteUtils.INT_32_BYTES;
 @NotNullByDefault
 class GroupFactoryImpl implements GroupFactory {
 
+	private static final byte[] FORMAT_VERSION_BYTES =
+			new byte[] {FORMAT_VERSION};
+
 	private final CryptoComponent crypto;
 
 	@Inject
@@ -31,7 +34,7 @@ class GroupFactoryImpl implements GroupFactory {
 	public Group createGroup(ClientId c, int majorVersion, byte[] descriptor) {
 		byte[] majorVersionBytes = new byte[INT_32_BYTES];
 		ByteUtils.writeUint32(majorVersion, majorVersionBytes, 0);
-		byte[] hash = crypto.hash(LABEL, new byte[] {FORMAT_VERSION},
+		byte[] hash = crypto.hash(LABEL, FORMAT_VERSION_BYTES,
 				StringUtils.toUtf8(c.getString()), majorVersionBytes,
 				descriptor);
 		return new Group(new GroupId(hash), c, majorVersion, 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 8e8151079fcbd3f3a71c4ca3efd5090708ab1d67..754f0edac58ed70f648ac2983ae70e21e35bf051 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
@@ -24,6 +24,9 @@ import static org.briarproject.bramble.util.ByteUtils.INT_64_BYTES;
 @NotNullByDefault
 class MessageFactoryImpl implements MessageFactory {
 
+	private static final byte[] FORMAT_VERSION_BYTES =
+			new byte[] {FORMAT_VERSION};
+
 	private final CryptoComponent crypto;
 
 	@Inject
@@ -35,14 +38,7 @@ class MessageFactoryImpl implements MessageFactory {
 	public Message createMessage(GroupId g, long timestamp, byte[] body) {
 		if (body.length > MAX_MESSAGE_BODY_LENGTH)
 			throw new IllegalArgumentException();
-		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[] idHash = crypto.hash(ID_LABEL, versionBytes, g.getBytes(),
-				timeBytes, rootHash);
-		MessageId id = new MessageId(idHash);
+		MessageId id = getMessageId(g, timestamp, body);
 		byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
 		System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
 		ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
@@ -50,6 +46,32 @@ class MessageFactoryImpl implements MessageFactory {
 		return new Message(id, g, timestamp, raw);
 	}
 
+	private MessageId getMessageId(GroupId g, long timestamp, byte[] body) {
+		// There's only one block, so the root hash is the hash of the block
+		byte[] rootHash = crypto.hash(BLOCK_LABEL, FORMAT_VERSION_BYTES, body);
+		byte[] timeBytes = new byte[INT_64_BYTES];
+		ByteUtils.writeUint64(timestamp, timeBytes, 0);
+		byte[] idHash = crypto.hash(ID_LABEL, FORMAT_VERSION_BYTES,
+				g.getBytes(), timeBytes, rootHash);
+		return new MessageId(idHash);
+	}
+
+	@Override
+	public Message createMessage(byte[] raw) {
+		if (raw.length < MESSAGE_HEADER_LENGTH)
+			throw new IllegalArgumentException();
+		if (raw.length > MAX_MESSAGE_LENGTH)
+			throw new IllegalArgumentException();
+		byte[] groupId = new byte[UniqueId.LENGTH];
+		System.arraycopy(raw, 0, groupId, 0, UniqueId.LENGTH);
+		GroupId g = new GroupId(groupId);
+		long timestamp = ByteUtils.readUint64(raw, UniqueId.LENGTH);
+		byte[] body = new byte[raw.length - MESSAGE_HEADER_LENGTH];
+		System.arraycopy(raw, MESSAGE_HEADER_LENGTH, body, 0, body.length);
+		MessageId id = getMessageId(g, timestamp, body);
+		return new Message(id, g, timestamp, raw);
+	}
+
 	@Override
 	public Message createMessage(MessageId m, byte[] raw) {
 		if (raw.length < MESSAGE_HEADER_LENGTH)
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java
index d7566979d218dd1f7c595568c60a619ce6a26685..3a7c6d057bcf21b85fdde144626576ab3d4345cd 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/sync/SyncRecordReaderImpl.java
@@ -6,7 +6,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.record.Record;
 import org.briarproject.bramble.api.record.RecordReader;
 import org.briarproject.bramble.api.sync.Ack;
-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;
@@ -126,20 +125,11 @@ class SyncRecordReaderImpl implements SyncRecordReader {
 		assert nextRecord != null;
 		byte[] payload = nextRecord.getPayload();
 		if (payload.length < MESSAGE_HEADER_LENGTH) throw new FormatException();
-		// Group ID
-		byte[] id = new byte[UniqueId.LENGTH];
-		System.arraycopy(payload, 0, id, 0, UniqueId.LENGTH);
-		GroupId groupId = new GroupId(id);
-		// Timestamp
+		// Validate timestamp
 		long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
 		if (timestamp < 0) throw new FormatException();
-		// Body
-		byte[] body = new byte[payload.length - MESSAGE_HEADER_LENGTH];
-		System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
-				payload.length - MESSAGE_HEADER_LENGTH);
 		nextRecord = null;
-		// TODO: Add a method that reuses the raw message
-		return messageFactory.createMessage(groupId, timestamp, body);
+		return messageFactory.createMessage(payload);
 	}
 
 	@Override