diff --git a/api/net/sf/briar/api/protocol/MessageParser.java b/api/net/sf/briar/api/protocol/MessageParser.java
deleted file mode 100644
index cd6ea58a85ebfc51a3d07b43bc47e21bd9c3353a..0000000000000000000000000000000000000000
--- a/api/net/sf/briar/api/protocol/MessageParser.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package net.sf.briar.api.protocol;
-
-import java.io.IOException;
-import java.security.GeneralSecurityException;
-
-public interface MessageParser {
-
-	Message parseMessage(byte[] raw) throws IOException,
-	GeneralSecurityException;
-}
diff --git a/api/net/sf/briar/api/serial/Reader.java b/api/net/sf/briar/api/serial/Reader.java
index 5e06136cf3669dd170144947f95ffc8e0a0120d3..e9fd9d0d27e93b0ad708f2a798d08b6102f173cb 100644
--- a/api/net/sf/briar/api/serial/Reader.java
+++ b/api/net/sf/briar/api/serial/Reader.java
@@ -59,5 +59,5 @@ public interface Reader {
 
 	boolean hasUserDefinedTag() throws IOException;
 	int readUserDefinedTag() throws IOException;
-	void readUserDefinedTag(int i) throws IOException;
+	void readUserDefinedTag(int tag) throws IOException;
 }
diff --git a/components/net/sf/briar/protocol/BundleReaderImpl.java b/components/net/sf/briar/protocol/BundleReaderImpl.java
index 219d131161c1bf29df1594102e9ce53447ca5bb4..2474a5e08553eb55a5e51b005fee88f1c4d25ed7 100644
--- a/components/net/sf/briar/protocol/BundleReaderImpl.java
+++ b/components/net/sf/briar/protocol/BundleReaderImpl.java
@@ -18,12 +18,9 @@ import net.sf.briar.api.protocol.BundleReader;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Header;
 import net.sf.briar.api.protocol.Message;
-import net.sf.briar.api.protocol.MessageParser;
-import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.FormatException;
-import net.sf.briar.api.serial.Raw;
-import net.sf.briar.api.serial.RawByteArray;
 import net.sf.briar.api.serial.Reader;
 
 class BundleReaderImpl implements BundleReader {
@@ -34,19 +31,19 @@ class BundleReaderImpl implements BundleReader {
 	private final PublicKey publicKey;
 	private final Signature signature;
 	private final MessageDigest messageDigest;
-	private final MessageParser messageParser;
+	private final MessageReader messageReader;
 	private final HeaderFactory headerFactory;
 	private final BatchFactory batchFactory;
 	private State state = State.START;
 
 	BundleReaderImpl(Reader reader, PublicKey publicKey, Signature signature,
-			MessageDigest messageDigest, MessageParser messageParser,
+			MessageDigest messageDigest, MessageReader messageParser,
 			HeaderFactory headerFactory, BatchFactory batchFactory) {
 		this.reader = reader;
 		this.publicKey = publicKey;
 		this.signature = signature;
 		this.messageDigest = messageDigest;
-		this.messageParser = messageParser;
+		this.messageReader = messageParser;
 		this.headerFactory = headerFactory;
 		this.batchFactory = batchFactory;
 	}
@@ -88,6 +85,7 @@ class BundleReaderImpl implements BundleReader {
 		// Timestamp
 		reader.readUserDefinedTag(Tags.TIMESTAMP);
 		long timestamp = reader.readInt64();
+		if(timestamp < 0L) throw new FormatException();
 		reader.removeConsumer(signing);
 		// Read and verify the signature
 		reader.readUserDefinedTag(Tags.SIGNATURE);
@@ -122,11 +120,11 @@ class BundleReaderImpl implements BundleReader {
 		reader.addConsumer(digesting);
 		reader.addConsumer(signing);
 		reader.readUserDefinedTag(Tags.BATCH);
-		List<Raw> rawMessages = new ArrayList<Raw>();
+		List<Message> messages = new ArrayList<Message>();
 		reader.readListStart();
 		while(!reader.hasListEnd()) {
 			reader.readUserDefinedTag(Tags.MESSAGE);
-			rawMessages.add(new RawByteArray(reader.readRaw()));
+			messages.add(messageReader.readMessage(reader));
 		}
 		reader.readListEnd();
 		reader.removeConsumer(signing);
@@ -136,12 +134,6 @@ class BundleReaderImpl implements BundleReader {
 		reader.removeConsumer(digesting);
 		reader.removeConsumer(counting);
 		if(!signature.verify(sig)) throw new SignatureException();
-		// Parse the messages
-		List<Message> messages = new ArrayList<Message>(rawMessages.size());
-		for(Raw r : rawMessages) {
-			Message m = messageParser.parseMessage(r.getBytes());
-			messages.add(m);
-		}
 		// Build and return the batch
 		BatchId id = new BatchId(messageDigest.digest());
 		return batchFactory.createBatch(id, messages);
diff --git a/components/net/sf/briar/protocol/BundleWriterImpl.java b/components/net/sf/briar/protocol/BundleWriterImpl.java
index 6454192636f53d0bdb884a582bd2c71f73b14dec..5640faae10416e02a5e3a9d03c3e82b650b52aab 100644
--- a/components/net/sf/briar/protocol/BundleWriterImpl.java
+++ b/components/net/sf/briar/protocol/BundleWriterImpl.java
@@ -53,13 +53,17 @@ class BundleWriterImpl implements BundleWriter {
 		// Write the data to be signed
 		out.setSigning(true);
 		writer.writeUserDefinedTag(Tags.HEADER);
+		// Acks
 		writer.writeListStart();
 		for(BatchId ack : acks) ack.writeTo(writer);
 		writer.writeListEnd();
+		// Subs
 		writer.writeListStart();
 		for(GroupId sub : subs) sub.writeTo(writer);
 		writer.writeListEnd();
+		// Transports
 		writer.writeMap(transports);
+		// Timestamp
 		writer.writeUserDefinedTag(Tags.TIMESTAMP);
 		writer.writeInt64(System.currentTimeMillis());
 		out.setSigning(false);
@@ -88,7 +92,8 @@ class BundleWriterImpl implements BundleWriter {
 		writer.writeListStart();
 		for(Raw message : messages) {
 			writer.writeUserDefinedTag(Tags.MESSAGE);
-			writer.writeRaw(message);
+			// Bypass the writer and write the raw message directly
+			out.write(message.getBytes());
 		}
 		writer.writeListEnd();
 		out.setSigning(false);
diff --git a/components/net/sf/briar/protocol/CopyingConsumer.java b/components/net/sf/briar/protocol/CopyingConsumer.java
new file mode 100644
index 0000000000000000000000000000000000000000..f84f5a746b9f6652fa10d841df134e9686eaa5ef
--- /dev/null
+++ b/components/net/sf/briar/protocol/CopyingConsumer.java
@@ -0,0 +1,27 @@
+package net.sf.briar.protocol;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+
+import net.sf.briar.api.serial.Consumer;
+
+public class CopyingConsumer implements Consumer {
+
+	private final ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+	byte[] getCopy() {
+		return out.toByteArray();
+	}
+
+	public void write(byte b) throws IOException {
+		out.write(b);
+	}
+
+	public void write(byte[] b) throws IOException {
+		out.write(b);
+	}
+
+	public void write(byte[] b, int off, int len) throws IOException {
+		out.write(b, off, len);
+	}
+}
diff --git a/components/net/sf/briar/protocol/MessageEncoderImpl.java b/components/net/sf/briar/protocol/MessageEncoderImpl.java
index cc15a7b26c62e2ace246744fb4e633e072019162..58d52b1be405ac7f254d37924c11f8b0a58a3c8f 100644
--- a/components/net/sf/briar/protocol/MessageEncoderImpl.java
+++ b/components/net/sf/briar/protocol/MessageEncoderImpl.java
@@ -12,6 +12,7 @@ import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.Writer;
 import net.sf.briar.api.serial.WriterFactory;
 
@@ -35,16 +36,20 @@ class MessageEncoderImpl implements MessageEncoder {
 		byte[] encodedKey = keyPair.getPublic().getEncoded();
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
-		w.writeRaw(parent);
-		w.writeRaw(group);
+		parent.writeTo(w);
+		group.writeTo(w);
+		w.writeUserDefinedTag(Tags.TIMESTAMP);
 		w.writeInt64(timestamp);
+		w.writeUserDefinedTag(Tags.AUTHOR);
 		w.writeString(nick);
 		w.writeRaw(encodedKey);
+		w.writeUserDefinedTag(Tags.MESSAGE_BODY);
 		w.writeRaw(body);
 		byte[] signable = out.toByteArray();
 		signature.initSign(keyPair.getPrivate());
 		signature.update(signable);
 		byte[] sig = signature.sign();
+		w.writeUserDefinedTag(Tags.SIGNATURE);
 		w.writeRaw(sig);
 		byte[] raw = out.toByteArray();
 		w.close();
@@ -53,10 +58,13 @@ class MessageEncoderImpl implements MessageEncoder {
 		messageDigest.update(raw);
 		MessageId id = new MessageId(messageDigest.digest());
 		// The author ID is the hash of the author's nick and public key
+		out.reset();
+		w = writerFactory.createWriter(out);
+		w.writeString(nick);
+		w.writeRaw(encodedKey);
+		w.close();
 		messageDigest.reset();
-		messageDigest.update(nick.getBytes("UTF-8"));
-		messageDigest.update((byte) 0); // Null separator
-		messageDigest.update(encodedKey);
+		messageDigest.update(out.toByteArray());
 		AuthorId author = new AuthorId(messageDigest.digest());
 		return new MessageImpl(id, parent, group, author, timestamp, raw);
 	}
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..904df2f90ea650431287db460cd714bccc7ce427
--- /dev/null
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -0,0 +1,13 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+
+import net.sf.briar.api.protocol.Message;
+import net.sf.briar.api.serial.Reader;
+
+interface MessageReader {
+
+	Message readMessage(Reader r) throws IOException,
+	GeneralSecurityException;
+}
diff --git a/components/net/sf/briar/protocol/MessageParserImpl.java b/components/net/sf/briar/protocol/MessageReaderImpl.java
similarity index 58%
rename from components/net/sf/briar/protocol/MessageParserImpl.java
rename to components/net/sf/briar/protocol/MessageReaderImpl.java
index dbc0d6675d160515896a353bc26437bbe73deaa7..cbc4a19e8e552e254ab4e2da14fe843e9dabcd53 100644
--- a/components/net/sf/briar/protocol/MessageParserImpl.java
+++ b/components/net/sf/briar/protocol/MessageReaderImpl.java
@@ -1,6 +1,5 @@
 package net.sf.briar.protocol;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.MessageDigest;
@@ -14,61 +13,63 @@ import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
-import net.sf.briar.api.protocol.MessageParser;
+import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.Reader;
-import net.sf.briar.api.serial.ReaderFactory;
 
-class MessageParserImpl implements MessageParser {
+class MessageReaderImpl implements MessageReader {
 
 	private final KeyParser keyParser;
 	private final Signature signature;
 	private final MessageDigest messageDigest;
-	private final ReaderFactory readerFactory;
 
-	MessageParserImpl(KeyParser keyParser, Signature signature,
-			MessageDigest messageDigest, ReaderFactory readerFactory) {
+	MessageReaderImpl(KeyParser keyParser, Signature signature,
+			MessageDigest messageDigest) {
 		this.keyParser = keyParser;
 		this.signature = signature;
 		this.messageDigest = messageDigest;
-		this.readerFactory = readerFactory;
 	}
 
-	public Message parseMessage(byte[] raw) throws IOException,
+	public Message readMessage(Reader reader) throws IOException,
 			GeneralSecurityException {
-		if(raw.length > Message.MAX_SIZE) throw new FormatException();
-		ByteArrayInputStream in = new ByteArrayInputStream(raw);
-		Reader r = readerFactory.createReader(in);
+		CopyingConsumer copying = new CopyingConsumer();
 		CountingConsumer counting = new CountingConsumer(Message.MAX_SIZE);
-		r.addConsumer(counting);
+		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
+		messageDigest.reset();
+		reader.addConsumer(copying);
+		reader.addConsumer(counting);
 		// Read the parent message ID
-		byte[] idBytes = r.readRaw();
-		if(idBytes.length != UniqueId.LENGTH) throw new FormatException();
-		MessageId parent = new MessageId(idBytes);
+		reader.readUserDefinedTag(Tags.MESSAGE_ID);
+		byte[] b = reader.readRaw();
+		if(b.length != UniqueId.LENGTH) throw new FormatException();
+		MessageId parent = new MessageId(b);
 		// Read the group ID
-		idBytes = r.readRaw();
-		if(idBytes.length != UniqueId.LENGTH) throw new FormatException();
-		GroupId group = new GroupId(idBytes);
+		reader.readUserDefinedTag(Tags.GROUP_ID);
+		b = reader.readRaw();
+		if(b.length != UniqueId.LENGTH) throw new FormatException();
+		GroupId group = new GroupId(b);
 		// Read the timestamp
-		long timestamp = r.readInt64();
+		reader.readUserDefinedTag(Tags.TIMESTAMP);
+		long timestamp = reader.readInt64();
+		if(timestamp < 0L) throw new FormatException();
 		// Hash the author's nick and public key to get the author ID
-		String nick = r.readString();
-		byte[] encodedKey = r.readRaw();
-		messageDigest.reset();
-		messageDigest.update(nick.getBytes("UTF-8"));
-		messageDigest.update((byte) 0); // Null separator
-		messageDigest.update(encodedKey);
+		reader.readUserDefinedTag(Tags.AUTHOR);
+		reader.addConsumer(digesting);
+		reader.readString();
+		byte[] encodedKey = reader.readRaw();
+		reader.removeConsumer(digesting);
 		AuthorId author = new AuthorId(messageDigest.digest());
 		// Skip the message body
-		r.readRaw();
+		reader.readUserDefinedTag(Tags.MESSAGE_BODY);
+		reader.readRaw();
 		// Record the length of the signed data
 		int messageLength = (int) counting.getCount();
-		r.removeConsumer(counting);
 		// Read the signature
-		byte[] sig = r.readRaw();
-		// That should be all
-		if(!r.eof()) throw new FormatException();
+		reader.readUserDefinedTag(Tags.SIGNATURE);
+		byte[] sig = reader.readRaw();
+		reader.removeConsumer(counting);
+		reader.removeConsumer(copying);
 		// Verify the signature
 		PublicKey publicKey;
 		try {
@@ -76,6 +77,7 @@ class MessageParserImpl implements MessageParser {
 		} catch(InvalidKeySpecException e) {
 			throw new FormatException();
 		}
+		byte[] raw = copying.getCopy();
 		signature.initVerify(publicKey);
 		signature.update(raw, 0, messageLength);
 		if(!signature.verify(sig)) throw new SignatureException();
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index 08e3c1b8ebe21fc4eba98ce1db5a26cf4414c582..18d350abb31b354ad70cd1497d1a84edca4b76ae 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -3,7 +3,6 @@ package net.sf.briar.protocol;
 import net.sf.briar.api.protocol.BundleReader;
 import net.sf.briar.api.protocol.BundleWriter;
 import net.sf.briar.api.protocol.MessageEncoder;
-import net.sf.briar.api.protocol.MessageParser;
 
 import com.google.inject.AbstractModule;
 
@@ -16,6 +15,6 @@ public class ProtocolModule extends AbstractModule {
 		bind(BundleWriter.class).to(BundleWriterImpl.class);
 		bind(HeaderFactory.class).to(HeaderFactoryImpl.class);
 		bind(MessageEncoder.class).to(MessageEncoderImpl.class);
-		bind(MessageParser.class).to(MessageParserImpl.class);
+		bind(MessageReader.class).to(MessageReaderImpl.class);
 	}
 }
diff --git a/components/net/sf/briar/serial/ReaderImpl.java b/components/net/sf/briar/serial/ReaderImpl.java
index a04112e1e31d83a18f31e00cd6123a10a26e6d00..ebdbf7af62a036f20d3c0b6ff4c6ed7f0ca5cca3 100644
--- a/components/net/sf/briar/serial/ReaderImpl.java
+++ b/components/net/sf/briar/serial/ReaderImpl.java
@@ -479,7 +479,7 @@ class ReaderImpl implements Reader {
 		}
 	}
 
-	public void readUserDefinedTag(int i) throws IOException {
-		if(readUserDefinedTag() != i) throw new FormatException();
+	public void readUserDefinedTag(int tag) throws IOException {
+		if(readUserDefinedTag() != tag) throw new FormatException();
 	}
 }
diff --git a/test/net/sf/briar/protocol/BundleReadWriteTest.java b/test/net/sf/briar/protocol/BundleReadWriteTest.java
index 9033a68600da95799c91456d24b3b8374d8003c4..8c5245b60ceb4a062cefd1824f12051ea18da32b 100644
--- a/test/net/sf/briar/protocol/BundleReadWriteTest.java
+++ b/test/net/sf/briar/protocol/BundleReadWriteTest.java
@@ -32,7 +32,6 @@ import net.sf.briar.api.protocol.Header;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
-import net.sf.briar.api.protocol.MessageParser;
 import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.Raw;
 import net.sf.briar.api.serial.RawByteArray;
@@ -71,8 +70,8 @@ public class BundleReadWriteTest extends TestCase {
 	private final WriterFactory wf;
 
 	private final KeyPair keyPair;
-	private final Signature sig;
-	private final MessageDigest dig;
+	private final Signature sig, sig1;
+	private final MessageDigest dig, dig1;
 	private final KeyParser keyParser;
 	private final Message message;
 
@@ -83,7 +82,9 @@ public class BundleReadWriteTest extends TestCase {
 		wf = i.getInstance(WriterFactory.class);
 		keyPair = KeyPairGenerator.getInstance(KEY_PAIR_ALGO).generateKeyPair();
 		sig = Signature.getInstance(SIGNATURE_ALGO);
+		sig1 = Signature.getInstance(SIGNATURE_ALGO);
 		dig = MessageDigest.getInstance(DIGEST_ALGO);
+		dig1 = MessageDigest.getInstance(DIGEST_ALGO);
 		final KeyFactory keyFactory = KeyFactory.getInstance(KEY_PAIR_ALGO);
 		keyParser = new KeyParser() {
 			public PublicKey parsePublicKey(byte[] encodedKey)
@@ -123,12 +124,12 @@ public class BundleReadWriteTest extends TestCase {
 
 		testWriteBundle();
 
-		MessageParser messageParser =
-			new MessageParserImpl(keyParser, sig, dig, rf);
+		MessageReader messageReader =
+			new MessageReaderImpl(keyParser, sig1, dig1);
 		FileInputStream in = new FileInputStream(bundle);
 		Reader reader = rf.createReader(in);
 		BundleReader r = new BundleReaderImpl(reader, keyPair.getPublic(), sig,
-				dig, messageParser, new HeaderFactoryImpl(),
+				dig, messageReader, new HeaderFactoryImpl(),
 				new BatchFactoryImpl());
 
 		Header h = r.getHeader();
@@ -163,12 +164,12 @@ public class BundleReadWriteTest extends TestCase {
 		f.writeByte(b + 1);
 		f.close();
 
-		MessageParser messageParser =
-			new MessageParserImpl(keyParser, sig, dig, rf);
+		MessageReader messageReader =
+			new MessageReaderImpl(keyParser, sig1, dig1);
 		FileInputStream in = new FileInputStream(bundle);
 		Reader reader = rf.createReader(in);
 		BundleReader r = new BundleReaderImpl(reader, keyPair.getPublic(), sig,
-				dig, messageParser, new HeaderFactoryImpl(),
+				dig, messageReader, new HeaderFactoryImpl(),
 				new BatchFactoryImpl());
 
 		Header h = r.getHeader();