diff --git a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
index 09e63bd98b87dcc11f26f84c045137a080f63d8f..e2893714dacc1bce8ab50b76684cfd9f2fd1f49d 100644
--- a/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/BlogManagerTest.java
@@ -3,6 +3,7 @@ package org.briarproject;
 import net.jodah.concurrentunit.Waiter;
 
 import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogCommentHeader;
 import org.briarproject.api.blogs.BlogFactory;
 import org.briarproject.api.blogs.BlogManager;
 import org.briarproject.api.blogs.BlogPost;
@@ -31,6 +32,7 @@ import org.briarproject.lifecycle.LifecycleModule;
 import org.briarproject.properties.PropertiesModule;
 import org.briarproject.sync.SyncModule;
 import org.briarproject.transport.TransportModule;
+import org.briarproject.util.StringUtils;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -50,6 +52,10 @@ import javax.inject.Inject;
 
 import static junit.framework.Assert.assertFalse;
 import static org.briarproject.TestPluginsModule.MAX_LATENCY;
+import static org.briarproject.api.blogs.MessageType.COMMENT;
+import static org.briarproject.api.blogs.MessageType.POST;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
 import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.briarproject.api.sync.ValidationManager.State.PENDING;
@@ -89,7 +95,6 @@ public class BlogManagerTest {
 	private final int TIMEOUT = 15000;
 	private final String AUTHOR1 = "Author 1";
 	private final String AUTHOR2 = "Author 2";
-	private final String CONTENT_TYPE = "text/plain";
 
 	private static final Logger LOG =
 			Logger.getLogger(ForumSharingIntegrationTest.class.getName());
@@ -172,15 +177,15 @@ public class BlogManagerTest {
 		defaultInit();
 
 		// check that blog0 has no posts
-		final byte[] body = TestUtils.getRandomBytes(42);
+		final String body = TestUtils.getRandomString(42);
 		Collection<BlogPostHeader> headers0 =
 				blogManager0.getPostHeaders(blog0.getId());
 		assertEquals(0, headers0.size());
 
 		// add a post to blog0
 		BlogPost p = blogPostFactory
-				.createBlogPost(blog0.getId(), null, clock.currentTimeMillis(),
-						null, author0, CONTENT_TYPE, body);
+				.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
+						author0, body);
 		blogManager0.addLocalPost(p);
 
 		// check that post is now in blog0
@@ -188,7 +193,7 @@ public class BlogManagerTest {
 		assertEquals(1, headers0.size());
 
 		// check that body is there
-		assertArrayEquals(body,
+		assertArrayEquals(StringUtils.toUtf8(body),
 				blogManager0.getPostBody(p.getMessage().getId()));
 
 		// make sure that blog0 at author1 doesn't have the post yet
@@ -203,9 +208,10 @@ public class BlogManagerTest {
 		// make sure post arrived
 		headers1 = blogManager1.getPostHeaders(blog0.getId());
 		assertEquals(1, headers1.size());
+		assertEquals(POST, headers1.iterator().next().getType());
 
 		// check that body is there
-		assertArrayEquals(body,
+		assertArrayEquals(StringUtils.toUtf8(body),
 				blogManager1.getPostBody(p.getMessage().getId()));
 
 		stopLifecycles();
@@ -217,10 +223,10 @@ public class BlogManagerTest {
 		defaultInit();
 
 		// add a post to blog1
-		final byte[] body = TestUtils.getRandomBytes(42);
+		final String body = TestUtils.getRandomString(42);
 		BlogPost p = blogPostFactory
-				.createBlogPost(blog1.getId(), null, clock.currentTimeMillis(),
-						null, author0, CONTENT_TYPE, body);
+				.createBlogPost(blog1.getId(), clock.currentTimeMillis(), null,
+						author0, body);
 		blogManager0.addLocalPost(p);
 
 		// check that post is now in blog1
@@ -287,6 +293,241 @@ public class BlogManagerTest {
 		stopLifecycles();
 	}
 
+	@Test
+	public void testBlogComment() throws Exception {
+		startLifecycles();
+		defaultInit();
+
+		// add a post to blog0
+		final String body = TestUtils.getRandomString(42);
+		BlogPost p = blogPostFactory
+				.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
+						author0, body);
+		blogManager0.addLocalPost(p);
+
+		// sync the post over
+		sync0To1();
+		deliveryWaiter.await(TIMEOUT, 1);
+
+		// make sure post arrived
+		Collection<BlogPostHeader> headers1 =
+				blogManager1.getPostHeaders(blog0.getId());
+		assertEquals(1, headers1.size());
+		assertEquals(POST, headers1.iterator().next().getType());
+
+		// 1 adds a comment to that blog post
+		String comment = "This is a comment on a blog post!";
+		blogManager1
+				.addLocalComment(author1, blog1.getId(), comment,
+						headers1.iterator().next());
+
+		// sync comment over
+		sync1To0();
+		deliveryWaiter.await(TIMEOUT, 2);
+
+		// make sure comment and wrapped post arrived
+		Collection<BlogPostHeader> headers0 =
+				blogManager0.getPostHeaders(blog1.getId());
+		assertEquals(1, headers0.size());
+		assertEquals(COMMENT, headers0.iterator().next().getType());
+		BlogCommentHeader h = (BlogCommentHeader) headers0.iterator().next();
+		assertEquals(author0, h.getParent().getAuthor());
+
+		// ensure that body can be retrieved from wrapped post
+		assertArrayEquals(StringUtils.toUtf8(body),
+				blogManager0.getPostBody(h.getParentId()));
+
+		// 1 has only their own comment in their blog
+		headers1 = blogManager1.getPostHeaders(blog1.getId());
+		assertEquals(1, headers1.size());
+
+		stopLifecycles();
+	}
+
+	@Test
+	public void testBlogCommentOnOwnPost() throws Exception {
+		startLifecycles();
+		defaultInit();
+
+		// add a post to blog0
+		final String body = TestUtils.getRandomString(42);
+		BlogPost p = blogPostFactory
+				.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
+						author0, body);
+		blogManager0.addLocalPost(p);
+
+		// get header of own post
+		Collection<BlogPostHeader> headers0 =
+				blogManager0.getPostHeaders(blog0.getId());
+		assertEquals(1, headers0.size());
+		BlogPostHeader header = headers0.iterator().next();
+
+		// add a comment on own post
+		String comment = "This is a comment on my own blog post!";
+		blogManager0
+				.addLocalComment(author0, blog0.getId(), comment, header);
+
+		// sync the post and comment over
+		sync0To1();
+		deliveryWaiter.await(TIMEOUT, 2);
+
+		// make sure post arrived
+		Collection<BlogPostHeader> headers1 =
+				blogManager1.getPostHeaders(blog0.getId());
+		assertEquals(2, headers1.size());
+
+		stopLifecycles();
+	}
+
+	@Test
+	public void testCommentOnComment() throws Exception {
+		startLifecycles();
+		defaultInit();
+
+		// add a post to blog0
+		final String body = TestUtils.getRandomString(42);
+		BlogPost p = blogPostFactory
+				.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
+						author0, body);
+		blogManager0.addLocalPost(p);
+
+		// sync the post over
+		sync0To1();
+		deliveryWaiter.await(TIMEOUT, 1);
+
+		// make sure post arrived
+		Collection<BlogPostHeader> headers1 =
+				blogManager1.getPostHeaders(blog0.getId());
+		assertEquals(1, headers1.size());
+		assertEquals(POST, headers1.iterator().next().getType());
+
+		// 1 reblogs that blog post
+		blogManager1
+				.addLocalComment(author1, blog1.getId(), null,
+						headers1.iterator().next());
+
+		// sync comment over
+		sync1To0();
+		deliveryWaiter.await(TIMEOUT, 2);
+
+		// make sure comment and wrapped post arrived
+		Collection<BlogPostHeader> headers0 =
+				blogManager0.getPostHeaders(blog1.getId());
+		assertEquals(1, headers0.size());
+
+		// get header of comment
+		BlogPostHeader cHeader = headers0.iterator().next();
+		assertEquals(COMMENT, cHeader.getType());
+
+		// comment on the comment
+		String comment = "This is a comment on a reblogged post.";
+		blogManager0
+				.addLocalComment(author0, blog0.getId(), comment, cHeader);
+
+		// sync comment over
+		sync0To1();
+		deliveryWaiter.await(TIMEOUT, 3);
+
+		// check that comment arrived
+		headers1 =
+				blogManager1.getPostHeaders(blog0.getId());
+		assertEquals(2, headers1.size());
+
+		// get header of comment
+		cHeader = null;
+		for (BlogPostHeader h : headers1) {
+			if (h.getType() == COMMENT) {
+				cHeader = h;
+			}
+		}
+		assertTrue(cHeader != null);
+
+		// another comment on the comment
+		String comment2 = "This is a comment on a comment.";
+		blogManager1.addLocalComment(author1, blog1.getId(), comment2, cHeader);
+
+		// sync comment over
+		sync1To0();
+		deliveryWaiter.await(TIMEOUT, 4);
+
+		// make sure new comment arrived
+		headers0 =
+				blogManager0.getPostHeaders(blog1.getId());
+		assertEquals(2, headers0.size());
+		boolean satisfied = false;
+		for (BlogPostHeader h : headers0) {
+			assertEquals(COMMENT, h.getType());
+			BlogCommentHeader c = (BlogCommentHeader) h;
+			if (c.getComment() != null && c.getComment().equals(comment2)) {
+				assertEquals(author0, c.getParent().getAuthor());
+				assertEquals(WRAPPED_COMMENT, c.getParent().getType());
+				assertEquals(comment,
+						((BlogCommentHeader) c.getParent()).getComment());
+				assertEquals(WRAPPED_COMMENT,
+						((BlogCommentHeader) c.getParent()).getParent()
+								.getType());
+				assertEquals(WRAPPED_POST,
+						((BlogCommentHeader) ((BlogCommentHeader) c
+								.getParent()).getParent()).getParent()
+								.getType());
+				satisfied = true;
+			}
+		}
+		assertTrue(satisfied);
+
+		stopLifecycles();
+	}
+
+	@Test
+	public void testCommentOnOwnComment() throws Exception {
+		startLifecycles();
+		defaultInit();
+
+		// add a post to blog0
+		final String body = TestUtils.getRandomString(42);
+		BlogPost p = blogPostFactory
+				.createBlogPost(blog0.getId(), clock.currentTimeMillis(), null,
+						author0, body);
+		blogManager0.addLocalPost(p);
+
+		// sync the post over
+		sync0To1();
+		deliveryWaiter.await(TIMEOUT, 1);
+
+		// make sure post arrived
+		Collection<BlogPostHeader> headers1 =
+				blogManager1.getPostHeaders(blog0.getId());
+		assertEquals(1, headers1.size());
+		assertEquals(POST, headers1.iterator().next().getType());
+
+		// 1 reblogs that blog post with a comment
+		String comment = "This is a comment on a post.";
+		blogManager1
+				.addLocalComment(author1, blog1.getId(), comment,
+						headers1.iterator().next());
+
+		// get comment from own blog
+		headers1 = blogManager1.getPostHeaders(blog1.getId());
+		assertEquals(1, headers1.size());
+		assertEquals(COMMENT, headers1.iterator().next().getType());
+		BlogCommentHeader ch = (BlogCommentHeader) headers1.iterator().next();
+		assertEquals(comment, ch.getComment());
+
+		comment = "This is a comment on a post with a comment.";
+		blogManager1.addLocalComment(author1, blog1.getId(), comment, ch);
+
+		// sync both comments over (2 comments + 1 wrapped post)
+		sync1To0();
+		deliveryWaiter.await(TIMEOUT, 3);
+
+		// make sure both comments arrived
+		Collection<BlogPostHeader> headers0 =
+				blogManager0.getPostHeaders(blog1.getId());
+		assertEquals(2, headers0.size());
+
+		stopLifecycles();
+	}
+
 	@After
 	public void tearDown() throws Exception {
 		TestUtils.deleteTestDirectory(testDir);
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
index 15552fdb1ac80bcba5149fa25509a24b76c86212..9906e511e80952f4a9c1352b405d38e7613e3b76 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogControllerImpl.java
@@ -212,9 +212,10 @@ public class BlogControllerImpl extends DbControllerImpl
 	}
 
 	private BlogPostHeader getPostHeader(MessageId m) throws DbException {
+		if (groupId == null) throw new IllegalStateException();
 		BlogPostHeader header = headerCache.get(m);
 		if (header == null) {
-			header = blogManager.getPostHeader(m);
+			header = blogManager.getPostHeader(groupId, m);
 			headerCache.put(m, header);
 		}
 		return header;
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
index 9c36a73a50c54d3f6276fa30bfe7deb0408ccdcc..f96deda0b2e9ecaeed0d79d3b9cea3adc0d2f3a8 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostFragment.java
@@ -136,11 +136,8 @@ public class BlogPostFragment extends BaseFragment {
 			ui.date.setText(AndroidUtils.formatDate(ctx, post.getTimestamp()));
 		}
 
-		if (post.getTitle() != null) {
-			ui.title.setText(post.getTitle());
-		} else {
-			ui.title.setVisibility(GONE);
-		}
+		// TODO remove #598
+		ui.title.setVisibility(GONE);
 
 		ui.body.setText(StringUtils.fromUtf8(post.getBody()));
 	}
diff --git a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
index c3aa850af251b88bb5b39092f014d3c57b33cdc8..55a439e2edfb148f28f8d44556f4f27c5188fea3 100644
--- a/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
+++ b/briar-android/src/org/briarproject/android/blogs/BlogPostItem.java
@@ -32,11 +32,6 @@ class BlogPostItem implements Comparable<BlogPostItem> {
 		return groupId;
 	}
 
-	@Nullable
-	public String getTitle() {
-		return header.getTitle();
-	}
-
 	public long getTimestamp() {
 		return header.getTimestamp();
 	}
@@ -72,11 +67,6 @@ class BlogPostItem implements Comparable<BlogPostItem> {
 		long aTime = getTimeReceived(), bTime = other.getTimeReceived();
 		if (aTime > bTime) return -1;
 		if (aTime < bTime) return 1;
-		// Break ties by post title
-		if (getTitle() != null && other.getTitle() != null) {
-			return String.CASE_INSENSITIVE_ORDER
-					.compare(getTitle(), other.getTitle());
-		}
 		return 0;
 	}
 }
diff --git a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
index 4e6d24312c74964e1b68a4004bd5843a2c82c9da..dc462331c764a6a04c4e9a5c39ab9892de342c9f 100644
--- a/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
+++ b/briar-android/src/org/briarproject/android/blogs/WriteBlogPostActivity.java
@@ -2,8 +2,6 @@ package org.briarproject.android.blogs;
 
 import android.content.Intent;
 import android.os.Bundle;
-import android.support.design.widget.TextInputEditText;
-import android.support.design.widget.TextInputLayout;
 import android.text.Editable;
 import android.text.TextWatcher;
 import android.view.KeyEvent;
@@ -39,19 +37,16 @@ import static android.view.View.GONE;
 import static android.view.View.VISIBLE;
 import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
-import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
 
 public class WriteBlogPostActivity extends BriarActivity
 		implements OnEditorActionListener {
 
 	private static final Logger LOG =
 			Logger.getLogger(WriteBlogPostActivity.class.getName());
-	private static final String contentType = "text/plain";
 
 	@Inject
 	protected AndroidNotificationManager notificationManager;
 
-	private TextInputEditText titleInput;
 	private EditText bodyInput;
 	private Button publishButton;
 	private ProgressBar progressBar;
@@ -76,16 +71,6 @@ public class WriteBlogPostActivity extends BriarActivity
 
 		setContentView(R.layout.activity_write_blog_post);
 
-		TextInputLayout titleLayout =
-				(TextInputLayout) findViewById(R.id.titleLayout);
-		if (titleLayout != null) {
-			titleLayout.setCounterMaxLength(MAX_BLOG_POST_TITLE_LENGTH);
-		}
-		titleInput = (TextInputEditText) findViewById(R.id.titleInput);
-		if (titleInput != null) {
-			titleInput.setOnEditorActionListener(this);
-		}
-
 		bodyInput = (EditText) findViewById(R.id.bodyInput);
 		bodyInput.addTextChangedListener(new TextWatcher() {
 			@Override
@@ -152,30 +137,24 @@ public class WriteBlogPostActivity extends BriarActivity
 	private void enableOrDisablePublishButton() {
 		int bodyLength =
 				StringUtils.toUtf8(bodyInput.getText().toString()).length;
-		if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH &&
-				titleInput.getText().length() <= MAX_BLOG_POST_TITLE_LENGTH)
+		if (bodyLength > 0 && bodyLength <= MAX_BLOG_POST_BODY_LENGTH)
 			publishButton.setEnabled(true);
 		else
 			publishButton.setEnabled(false);
 	}
 
 	private void publish() {
-		// title
-		String title = titleInput.getText().toString();
-		if (title.length() > MAX_BLOG_POST_TITLE_LENGTH) return;
-		if (title.length() == 0) title = null;
-
 		// body
-		byte[] body = StringUtils.toUtf8(bodyInput.getText().toString());
+		String body = bodyInput.getText().toString();
 
 		// hide publish button, show progress bar
 		publishButton.setVisibility(GONE);
 		progressBar.setVisibility(VISIBLE);
 
-		storePost(title, body);
+		storePost(body);
 	}
 
-	private void storePost(final String title, final byte[] body) {
+	private void storePost(final String body) {
 		runOnDbThread(new Runnable() {
 			@Override
 			public void run() {
@@ -185,8 +164,7 @@ public class WriteBlogPostActivity extends BriarActivity
 							identityManager.getLocalAuthors();
 					LocalAuthor author = authors.iterator().next();
 					BlogPost p = blogPostFactory
-							.createBlogPost(groupId, title, now, null, author,
-									contentType, body);
+							.createBlogPost(groupId, now, null, author, body);
 					blogManager.addLocalPost(p);
 					postPublished();
 				} catch (DbException | GeneralSecurityException | FormatException e) {
diff --git a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
index 638074ca13753aa13438866160f96bbfe343144b..2ec3bc09be9256e004949e1446c77f04aeffa0bc 100644
--- a/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
+++ b/briar-android/src/org/briarproject/android/forum/ForumControllerImpl.java
@@ -404,7 +404,7 @@ public class ForumControllerImpl extends DbControllerImpl
 		ForumPostHeader h =
 				new ForumPostHeader(p.getMessage().getId(), p.getParent(),
 						p.getMessage().getTimestamp(), p.getAuthor(), VERIFIED,
-						p.getContentType(), false);
+						false);
 		addNewPost(h);
 	}
 
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java
new file mode 100644
index 0000000000000000000000000000000000000000..a5d2e4e9df68def851974bb3a896e03c21aefb1f
--- /dev/null
+++ b/briar-api/src/org/briarproject/api/blogs/BlogCommentHeader.java
@@ -0,0 +1,42 @@
+package org.briarproject.api.blogs;
+
+import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.Author.Status;
+import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import static org.briarproject.api.blogs.MessageType.COMMENT;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
+
+public class BlogCommentHeader extends BlogPostHeader {
+
+	private final String comment;
+	private final BlogPostHeader parent;
+
+	public BlogCommentHeader(@NotNull MessageType type,
+			@NotNull GroupId groupId, @Nullable String comment,
+			@NotNull BlogPostHeader parent, @NotNull MessageId id,
+			long timestamp, long timeReceived, @NotNull Author author,
+			@NotNull Status authorStatus, boolean read) {
+
+		super(type, groupId, id, parent.getId(), timestamp,
+				timeReceived, author, authorStatus, read);
+
+		if (type != COMMENT && type != WRAPPED_COMMENT)
+			throw new IllegalArgumentException("Incompatible Message Type");
+
+		this.comment = comment;
+		this.parent = parent;
+	}
+
+	@Nullable
+	public String getComment() {
+		return comment;
+	}
+
+	public BlogPostHeader getParent() {
+		return parent;
+	}
+}
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
index 5a9bff7f4e0c3b1b0c544964c2d690397f3e1d04..13e450c1c77843198469dfc8746afbbf09dd64f4 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogConstants.java
@@ -10,12 +10,6 @@ public interface BlogConstants {
 	/** The length of a blogs's description in UTF-8 bytes. */
 	int MAX_BLOG_DESC_LENGTH = 240;
 
-	/** The maximum length of a blog post's content type in UTF-8 bytes. */
-	int MAX_CONTENT_TYPE_LENGTH = 50;
-
-	/** The length of a blog post's title in UTF-8 bytes. */
-	int MAX_BLOG_POST_TITLE_LENGTH = 100;
-
 	/** The maximum length of a blog post's body in bytes. */
 	int MAX_BLOG_POST_BODY_LENGTH = MAX_MESSAGE_BODY_LENGTH - 1024;
 
@@ -31,17 +25,20 @@ public interface BlogConstants {
 	// Metadata keys
 	String KEY_TYPE = "type";
 	String KEY_DESCRIPTION = "description";
-	String KEY_TITLE = "title";
 	String KEY_TIMESTAMP = "timestamp";
 	String KEY_TIME_RECEIVED = "timeReceived";
 	String KEY_AUTHOR_ID = "id";
 	String KEY_AUTHOR_NAME = "name";
 	String KEY_PUBLIC_KEY = "publicKey";
 	String KEY_AUTHOR = "author";
-	String KEY_CONTENT_TYPE = "contentType";
 	String KEY_READ = "read";
 	String KEY_COMMENT = "comment";
 	String KEY_ORIGINAL_MSG_ID = "originalMessageId";
-	String KEY_CURRENT_MSG_ID = "currentMessageId";
+	String KEY_ORIGINAL_PARENT_MSG_ID = "originalParentMessageId";
+	/**
+	 * This is the ID of either a message wrapped from a different group
+	 * or of a message from the same group that therefore needed no wrapping.
+	 */
+	String KEY_PARENT_MSG_ID = "parentMessageId";
 
 }
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogManager.java b/briar-api/src/org/briarproject/api/blogs/BlogManager.java
index 1d4f0eaeebee2cf20fc018851c8aa1b6b6fe8f23..ac8ea516b47f4269a51f8fca2c0d68a4d0fc1437 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogManager.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogManager.java
@@ -7,6 +7,7 @@ import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.Collection;
 
@@ -31,6 +32,11 @@ public interface BlogManager {
 	/** Stores a local blog post. */
 	void addLocalPost(Transaction txn, BlogPost p) throws DbException;
 
+	/** Add a comment to an existing blog post or reblog it. */
+	void addLocalComment(LocalAuthor author, GroupId groupId,
+			@Nullable String comment, BlogPostHeader wHeader)
+			throws DbException;
+
 	/** Returns the blog with the given ID. */
 	Blog getBlog(GroupId g) throws DbException;
 
@@ -47,7 +53,7 @@ public interface BlogManager {
 	Collection<Blog> getBlogs() throws DbException;
 
 	/** Returns the header of the blog post with the given ID. */
-	BlogPostHeader getPostHeader(MessageId m) throws DbException;
+	BlogPostHeader getPostHeader(GroupId g, MessageId m) throws DbException;
 
 	/** Returns the body of the blog post with the given ID. */
 	byte[] getPostBody(MessageId m) throws DbException;
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPost.java b/briar-api/src/org/briarproject/api/blogs/BlogPost.java
index 3854c047279b90f2718a39644cee381893ad9a5b..73c787b29dbd1776b9bf0eff9e75db144411a69c 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogPost.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogPost.java
@@ -9,19 +9,8 @@ import org.jetbrains.annotations.Nullable;
 
 public class BlogPost extends ForumPost {
 
-	@Nullable
-	private final String title;
-
-	public BlogPost(@Nullable String title,	@NotNull Message message,
-			@Nullable MessageId parent,	@NotNull Author author,
-			@NotNull String contentType) {
-		super(message, parent, author, contentType);
-
-		this.title = title;
-	}
-
-	@Nullable
-	public String getTitle() {
-		return title;
+	public BlogPost(@NotNull Message message, @Nullable MessageId parent,
+			@NotNull Author author) {
+		super(message, parent, author);
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java
index 6ced9ba20fe646822eced9d1269648f0740e51f2..7b05c0078ec5d5ba84e86412e5291020b1c196a1 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogPostFactory.java
@@ -1,8 +1,10 @@
 package org.briarproject.api.blogs;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.data.BdfList;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.GroupId;
+import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -11,9 +13,30 @@ import java.security.GeneralSecurityException;
 
 public interface BlogPostFactory {
 
-	BlogPost createBlogPost(@NotNull GroupId groupId, @Nullable String title,
-			long timestamp, @Nullable MessageId parent,
-			@NotNull LocalAuthor author, @NotNull String contentType,
-			@NotNull byte[] body)
+	BlogPost createBlogPost(@NotNull GroupId groupId, long timestamp,
+			@Nullable MessageId parent, @NotNull LocalAuthor author,
+			@NotNull String body)
 			throws FormatException, GeneralSecurityException;
+
+	Message createBlogComment(GroupId groupId, LocalAuthor author,
+			@Nullable String comment, MessageId originalId, MessageId wrappedId)
+			throws FormatException, GeneralSecurityException;
+
+	/** Wraps a blog post */
+	Message wrapPost(GroupId groupId, byte[] descriptor,
+			long timestamp, BdfList body)
+			throws FormatException;
+
+	/** Re-wraps a previously wrapped post */
+	Message rewrapWrappedPost(GroupId groupId, BdfList body)
+			throws FormatException;
+
+	/** Wraps a blog comment */
+	Message wrapComment(GroupId groupId, byte[] descriptor,
+			long timestamp, BdfList body, MessageId currentId)
+			throws FormatException;
+
+	/** Re-wraps a previously wrapped comment */
+	Message rewrapWrappedComment(GroupId groupId, BdfList body,
+			MessageId currentId) throws FormatException;
 }
diff --git a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java
index f9ef7bdfeaa7d6e5184b469734d2ba167c8f0b50..dc191078dfa8af7cd0bbfc44ff81544c00cc718a 100644
--- a/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java
+++ b/briar-api/src/org/briarproject/api/blogs/BlogPostHeader.java
@@ -3,33 +3,45 @@ package org.briarproject.api.blogs;
 import org.briarproject.api.clients.PostHeader;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.Author.Status;
+import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.MessageId;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 public class BlogPostHeader extends PostHeader {
 
-	@Nullable
-	private final String title;
+	private final MessageType type;
+	private final GroupId groupId;
 	private final long timeReceived;
 
-	public BlogPostHeader(@Nullable String title, @NotNull MessageId id,
-			long timestamp, long timeReceived, @NotNull Author author,
-			@NotNull Status authorStatus, @NotNull String contentType,
-			boolean read) {
-		super(id, null, timestamp, author, authorStatus, contentType, read);
+	public BlogPostHeader(@NotNull MessageType type, @NotNull GroupId groupId,
+			@NotNull MessageId id, @Nullable MessageId parentId, long timestamp,
+			long timeReceived, @NotNull Author author,
+			@NotNull Status authorStatus, boolean read) {
+		super(id, parentId, timestamp, author, authorStatus, read);
 
-		this.title = title;
+		this.type = type;
+		this.groupId = groupId;
 		this.timeReceived = timeReceived;
 	}
 
-	@Nullable
-	public String getTitle() {
-		return title;
+	public BlogPostHeader(@NotNull MessageType type, @NotNull GroupId groupId,
+			@NotNull MessageId id, long timestamp, long timeReceived,
+			@NotNull Author author, @NotNull Status authorStatus,
+			boolean read) {
+		this(type, groupId, id, null, timestamp, timeReceived, author,
+				authorStatus, read);
+	}
+
+	public MessageType getType() {
+		return type;
+	}
+
+	public GroupId getGroupId() {
+		return groupId;
 	}
 
 	public long getTimeReceived() {
 		return timeReceived;
 	}
-
 }
diff --git a/briar-api/src/org/briarproject/api/clients/ClientHelper.java b/briar-api/src/org/briarproject/api/clients/ClientHelper.java
index a3524d2767d17e188249ec3390b140c283e4021a..c6553c91bb650e25ee96040f0edb6ba8e65ec068 100644
--- a/briar-api/src/org/briarproject/api/clients/ClientHelper.java
+++ b/briar-api/src/org/briarproject/api/clients/ClientHelper.java
@@ -10,6 +10,7 @@ import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
 
+import java.security.GeneralSecurityException;
 import java.util.Map;
 
 public interface ClientHelper {
@@ -72,7 +73,7 @@ public interface ClientHelper {
 	/**
 	 * Marks the given message as shared or unshared with other contacts.
 	 */
-	void setMessageShared(Transaction txn, Message m, boolean shared)
+	void setMessageShared(Transaction txn, MessageId m, boolean shared)
 			throws DbException;
 
 	byte[] toByteArray(BdfDictionary dictionary) throws FormatException;
@@ -83,4 +84,9 @@ public interface ClientHelper {
 			throws FormatException;
 
 	BdfList toList(byte[] b, int off, int len) throws FormatException;
+
+	BdfList toList(byte[] b) throws FormatException;
+
+	byte[] sign(BdfList toSign, byte[] privateKey)
+			throws FormatException, GeneralSecurityException;
 }
diff --git a/briar-api/src/org/briarproject/api/clients/PostHeader.java b/briar-api/src/org/briarproject/api/clients/PostHeader.java
index 33f0d28771c9ecc7f30eeb87798d18b3b3f4c1b2..7ffed63645bb5a057712cdb30b389a86a17e50dd 100644
--- a/briar-api/src/org/briarproject/api/clients/PostHeader.java
+++ b/briar-api/src/org/briarproject/api/clients/PostHeader.java
@@ -1,6 +1,7 @@
 package org.briarproject.api.clients;
 
 import org.briarproject.api.identity.Author;
+import org.briarproject.api.identity.Author.Status;
 import org.briarproject.api.sync.MessageId;
 
 public abstract class PostHeader {
@@ -9,19 +10,16 @@ public abstract class PostHeader {
 	private final MessageId parentId;
 	private final long timestamp;
 	private final Author author;
-	private final Author.Status authorStatus;
-	private final String contentType;
+	private final Status authorStatus;
 	private final boolean read;
 
 	public PostHeader(MessageId id, MessageId parentId, long timestamp,
-			Author author, Author.Status authorStatus, String contentType,
-			boolean read) {
+			Author author, Status authorStatus, boolean read) {
 		this.id = id;
 		this.parentId = parentId;
 		this.timestamp = timestamp;
 		this.author = author;
 		this.authorStatus = authorStatus;
-		this.contentType = contentType;
 		this.read = read;
 	}
 
@@ -33,14 +31,10 @@ public abstract class PostHeader {
 		return author;
 	}
 
-	public Author.Status getAuthorStatus() {
+	public Status getAuthorStatus() {
 		return authorStatus;
 	}
 
-	public String getContentType() {
-		return contentType;
-	}
-
 	public long getTimestamp() {
 		return timestamp;
 	}
diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 735f64a21fedb927bcc60b3d8cfb8d83339b8fe3..d3dbace30d300d794fa593ab6ecec34f0ff4d7cb 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -437,7 +437,7 @@ public interface DatabaseComponent {
 	/**
 	 * Marks the given message as shared or unshared.
 	 */
-	void setMessageShared(Transaction txn, Message m, boolean shared)
+	void setMessageShared(Transaction txn, MessageId m, boolean shared)
 			throws DbException;
 
 	/**
diff --git a/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java b/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java
index d011e8c3ebe5394e2dccb13c22f423c827db725e..840c7cc42d6808497f98bea0cbde985e008ccbb5 100644
--- a/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java
+++ b/briar-api/src/org/briarproject/api/event/MessageSharedEvent.java
@@ -1,17 +1,17 @@
 package org.briarproject.api.event;
 
-import org.briarproject.api.sync.Message;
+import org.briarproject.api.sync.MessageId;
 
 /** An event that is broadcast when a message is shared. */
 public class MessageSharedEvent extends Event {
 
-	private final Message message;
+	private final MessageId messageId;
 
-	public MessageSharedEvent(Message message) {
-		this.message = message;
+	public MessageSharedEvent(MessageId message) {
+		this.messageId = message;
 	}
 
-	public Message getMessage() {
-		return message;
+	public MessageId getMessageId() {
+		return messageId;
 	}
 }
diff --git a/briar-api/src/org/briarproject/api/forum/ForumConstants.java b/briar-api/src/org/briarproject/api/forum/ForumConstants.java
index 9523b20cc8eb1fbdd73974a6461671acc19a08a6..97557d5fe405fbefa3cdb45899e6ebeeb2219248 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumConstants.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumConstants.java
@@ -27,7 +27,6 @@ public interface ForumConstants {
 	String KEY_NAME = "name";
 	String KEY_PUBLIC_NAME = "publicKey";
 	String KEY_AUTHOR = "author";
-	String KEY_CONTENT_TYPE = "contentType";
 	String KEY_LOCAL = "local";
 	String KEY_READ = "read";
 
diff --git a/briar-api/src/org/briarproject/api/forum/ForumPost.java b/briar-api/src/org/briarproject/api/forum/ForumPost.java
index 404d6c520e8cb33661298fa48a7acc123c278e31..6e433137b4edf6270aec7bdae2d407126fabee9d 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumPost.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumPost.java
@@ -9,14 +9,11 @@ public class ForumPost {
 	private final Message message;
 	private final MessageId parent;
 	private final Author author;
-	private final String contentType;
 
-	public ForumPost(Message message, MessageId parent, Author author,
-			String contentType) {
+	public ForumPost(Message message, MessageId parent, Author author) {
 		this.message = message;
 		this.parent = parent;
 		this.author = author;
-		this.contentType = contentType;
 	}
 
 	public Message getMessage() {
@@ -30,8 +27,4 @@ public class ForumPost {
 	public Author getAuthor() {
 		return author;
 	}
-
-	public String getContentType() {
-		return contentType;
-	}
 }
diff --git a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java
index c25a9e251b1436e3beda8207656abba1112a0674..ef08dc022fa32d758bca4d0280b8a6a541fa22f7 100644
--- a/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java
+++ b/briar-api/src/org/briarproject/api/forum/ForumPostHeader.java
@@ -9,9 +9,8 @@ public class ForumPostHeader extends PostHeader
 		implements MessageTree.MessageNode {
 
 	public ForumPostHeader(MessageId id, MessageId parentId, long timestamp,
-			Author author, Author.Status authorStatus, String contentType,
-			boolean read) {
-		super(id, parentId, timestamp, author, authorStatus, contentType, read);
+			Author author, Author.Status authorStatus, boolean read) {
+		super(id, parentId, timestamp, author, authorStatus, read);
 	}
 
 }
diff --git a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java
index 82f7d7ab2848104dc492f169e184194f69c24e1c..7853ec403c2cb08321266bc7914eeefc5c826a54 100644
--- a/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java
+++ b/briar-core/src/org/briarproject/blogs/BlogFactoryImpl.java
@@ -59,7 +59,7 @@ class BlogFactoryImpl implements BlogFactory {
 
 		byte[] descriptor = g.getDescriptor();
 		// Blog Name, Author Name, Public Key
-		BdfList blog = clientHelper.toList(descriptor, 0, descriptor.length);
+		BdfList blog = clientHelper.toList(descriptor);
 		String name = blog.getString(0);
 		Author a =
 				authorFactory.createAuthor(blog.getString(1), blog.getRaw(2));
diff --git a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
index 097e594f46c53dc8ad614cca33475b610b94678e..2720910e0c1f1ca77a7f3e93fc2950f582215fc3 100644
--- a/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
+++ b/briar-core/src/org/briarproject/blogs/BlogManagerImpl.java
@@ -2,10 +2,13 @@ package org.briarproject.blogs;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.blogs.Blog;
+import org.briarproject.api.blogs.BlogCommentHeader;
 import org.briarproject.api.blogs.BlogFactory;
 import org.briarproject.api.blogs.BlogManager;
 import org.briarproject.api.blogs.BlogPost;
+import org.briarproject.api.blogs.BlogPostFactory;
 import org.briarproject.api.blogs.BlogPostHeader;
+import org.briarproject.api.blogs.MessageType;
 import org.briarproject.api.clients.Client;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.Contact;
@@ -33,10 +36,14 @@ import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.clients.BdfIncomingMessageHook;
 import org.briarproject.util.StringUtils;
+import org.jetbrains.annotations.Nullable;
 
+import java.security.GeneralSecurityException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -45,19 +52,26 @@ import java.util.logging.Logger;
 
 import javax.inject.Inject;
 
-import static java.util.logging.Level.WARNING;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
-import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
+import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
 import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
+import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
+import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
 import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
-import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
+import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
+import static org.briarproject.api.blogs.MessageType.COMMENT;
+import static org.briarproject.api.blogs.MessageType.POST;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
 import static org.briarproject.api.contact.ContactManager.AddContactHook;
 import static org.briarproject.api.contact.ContactManager.RemoveContactHook;
+import static org.briarproject.blogs.BlogPostValidator.authorToBdfDictionary;
 
 class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		AddContactHook, RemoveContactHook, Client,
@@ -74,18 +88,21 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	private final IdentityManager identityManager;
 	private final ContactManager contactManager;
 	private final BlogFactory blogFactory;
+	private final BlogPostFactory blogPostFactory;
 	private final List<RemoveBlogHook> removeHooks;
 
 	@Inject
 	BlogManagerImpl(DatabaseComponent db, IdentityManager identityManager,
 			ClientHelper clientHelper, MetadataParser metadataParser,
-			ContactManager contactManager, BlogFactory blogFactory) {
+			ContactManager contactManager, BlogFactory blogFactory,
+			BlogPostFactory blogPostFactory) {
 		super(clientHelper, metadataParser);
 
 		this.db = db;
 		this.identityManager = identityManager;
 		this.contactManager = contactManager;
 		this.blogFactory = blogFactory;
+		this.blogPostFactory = blogPostFactory;
 		removeHooks = new CopyOnWriteArrayList<RemoveBlogHook>();
 	}
 
@@ -160,13 +177,43 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	protected void incomingMessage(Transaction txn, Message m, BdfList list,
 			BdfDictionary meta) throws DbException, FormatException {
 
-		clientHelper.setMessageShared(txn, m, true);
-
 		GroupId groupId = m.getGroupId();
-		BlogPostHeader h = getPostHeaderFromMetadata(txn, m.getId(), meta);
-		BlogPostAddedEvent event =
-				new BlogPostAddedEvent(groupId, h, false);
-		txn.attach(event);
+		MessageType type = getMessageType(meta);
+
+		if (type == POST || type == COMMENT) {
+			BlogPostHeader h =
+					getPostHeaderFromMetadata(txn, groupId, m.getId(), meta);
+
+			// check that original message IDs match
+			if (type == COMMENT) {
+				BdfDictionary d = clientHelper
+						.getMessageMetadataAsDictionary(txn, h.getParentId());
+				byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID);
+				byte[] original2 = meta.getRaw(KEY_ORIGINAL_PARENT_MSG_ID);
+				if (!Arrays.equals(original1, original2)) {
+					throw new FormatException();
+				}
+			}
+			// share dependencies recursively - TODO remove with #598
+			share(txn, h);
+
+			// broadcast event about new post or comment
+			BlogPostAddedEvent event =
+					new BlogPostAddedEvent(groupId, h, false);
+			txn.attach(event);
+		} else if (type == WRAPPED_COMMENT) {
+			// Check that the original message ID in the dependency's metadata
+			// matches the original parent ID of the wrapped comment
+			MessageId dependencyId =
+					new MessageId(meta.getRaw(KEY_PARENT_MSG_ID));
+			BdfDictionary d = clientHelper
+					.getMessageMetadataAsDictionary(txn, dependencyId);
+			byte[] original1 = d.getRaw(KEY_ORIGINAL_MSG_ID);
+			byte[] original2 = meta.getRaw(KEY_ORIGINAL_PARENT_MSG_ID);
+			if (!Arrays.equals(original1, original2)) {
+				throw new FormatException();
+			}
+		}
 	}
 
 	@Override
@@ -247,17 +294,9 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	public void addLocalPost(Transaction txn, BlogPost p) throws DbException {
 		try {
 			BdfDictionary meta = new BdfDictionary();
-			if (p.getTitle() != null) meta.put(KEY_TITLE, p.getTitle());
+			meta.put(KEY_TYPE, POST.getInt());
 			meta.put(KEY_TIMESTAMP, p.getMessage().getTimestamp());
-
-			Author a = p.getAuthor();
-			BdfDictionary authorMeta = new BdfDictionary();
-			authorMeta.put(KEY_AUTHOR_ID, a.getId());
-			authorMeta.put(KEY_AUTHOR_NAME, a.getName());
-			authorMeta.put(KEY_PUBLIC_KEY, a.getPublicKey());
-			meta.put(KEY_AUTHOR, authorMeta);
-
-			meta.put(KEY_CONTENT_TYPE, p.getContentType());
+			meta.put(KEY_AUTHOR, authorToBdfDictionary(p.getAuthor()));
 			meta.put(KEY_READ, true);
 			clientHelper.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta,
 					true);
@@ -265,7 +304,8 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 			// broadcast event about new post
 			GroupId groupId = p.getMessage().getGroupId();
 			MessageId postId = p.getMessage().getId();
-			BlogPostHeader h = getPostHeaderFromMetadata(txn, postId, meta);
+			BlogPostHeader h =
+					getPostHeaderFromMetadata(txn, groupId, postId, meta);
 			BlogPostAddedEvent event =
 					new BlogPostAddedEvent(groupId, h, true);
 			txn.attach(event);
@@ -274,6 +314,125 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		}
 	}
 
+	@Override
+	public void addLocalComment(LocalAuthor author, GroupId groupId,
+			@Nullable String comment, BlogPostHeader pOriginalHeader)
+			throws DbException {
+
+		MessageType type = pOriginalHeader.getType();
+		if (type != POST && type != COMMENT)
+			throw new IllegalArgumentException("Comment on unknown type!");
+
+		Transaction txn = db.startTransaction(false);
+		try {
+			// Wrap post that we are commenting on
+			MessageId parentId = wrapMessage(txn, groupId, pOriginalHeader);
+
+			// Get ID of new parent's original message.
+			// Assumes that pOriginalHeader is a POST or COMMENT
+			MessageId pOriginalId = pOriginalHeader.getId();
+
+			// Create actual comment
+			Message message = blogPostFactory
+					.createBlogComment(groupId, author, comment, pOriginalId,
+							parentId);
+			BdfDictionary meta = new BdfDictionary();
+			meta.put(KEY_TYPE, COMMENT.getInt());
+			if (comment != null) meta.put(KEY_COMMENT, comment);
+			meta.put(KEY_TIMESTAMP, message.getTimestamp());
+			meta.put(KEY_ORIGINAL_MSG_ID, message.getId());
+			meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
+			meta.put(KEY_PARENT_MSG_ID, parentId);
+			meta.put(KEY_AUTHOR, authorToBdfDictionary(author));
+
+			// Send comment
+			clientHelper.addLocalMessage(txn, message, CLIENT_ID, meta, true);
+
+			// broadcast event
+			BlogPostHeader h =
+					getPostHeaderFromMetadata(txn, groupId, message.getId(),
+							meta);
+			BlogPostAddedEvent event = new BlogPostAddedEvent(groupId, h, true);
+			txn.attach(event);
+			txn.setComplete();
+		} catch (FormatException e) {
+			throw new DbException(e);
+		} catch (GeneralSecurityException e) {
+			throw new IllegalArgumentException("Invalid key of author", e);
+		} finally {
+			//noinspection ThrowFromFinallyBlock
+			db.endTransaction(txn);
+		}
+	}
+
+	private MessageId wrapMessage(Transaction txn, GroupId groupId,
+			BlogPostHeader pOriginalHeader)
+			throws DbException, FormatException {
+
+		if (groupId.equals(pOriginalHeader.getGroupId())) {
+			// We are trying to wrap a post that is already in our group.
+			// This is unnecessary, so just return the post's MessageId
+			return pOriginalHeader.getId();
+		}
+
+		// Get body of message to be wrapped
+		BdfList body =
+				clientHelper.getMessageAsList(txn, pOriginalHeader.getId());
+		long wTimestamp = pOriginalHeader.getTimestamp();
+		Message wMessage;
+
+		BdfDictionary meta = new BdfDictionary();
+		MessageType type = pOriginalHeader.getType();
+		if (type == POST) {
+			Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
+			byte[] wDescriptor = wGroup.getDescriptor();
+			// Wrap post
+			wMessage = blogPostFactory
+					.wrapPost(groupId, wDescriptor, wTimestamp, body);
+			meta.put(KEY_TYPE, WRAPPED_POST.getInt());
+		} else if (type == COMMENT) {
+			Group wGroup = db.getGroup(txn, pOriginalHeader.getGroupId());
+			byte[] wDescriptor = wGroup.getDescriptor();
+			BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
+			MessageId wrappedId =
+					wrapMessage(txn, groupId, wComment.getParent());
+			// Wrap comment
+			wMessage = blogPostFactory
+					.wrapComment(groupId, wDescriptor, wTimestamp,
+							body, wrappedId);
+			meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
+			if(wComment.getComment() != null)
+				meta.put(KEY_COMMENT, wComment.getComment());
+			meta.put(KEY_PARENT_MSG_ID, wrappedId);
+		} else if (type == WRAPPED_POST) {
+			// Re-wrap wrapped post without adding another wrapping layer
+			wMessage = blogPostFactory.rewrapWrappedPost(groupId, body);
+			meta.put(KEY_TYPE, WRAPPED_POST.getInt());
+		} else if (type == WRAPPED_COMMENT) {
+			BlogCommentHeader wComment = (BlogCommentHeader) pOriginalHeader;
+			MessageId wrappedId =
+					wrapMessage(txn, groupId, wComment.getParent());
+			// Re-wrap wrapped comment
+			wMessage = blogPostFactory
+					.rewrapWrappedComment(groupId, body, wrappedId);
+			meta.put(KEY_TYPE, WRAPPED_COMMENT.getInt());
+			if(wComment.getComment() != null)
+				meta.put(KEY_COMMENT, wComment.getComment());
+			meta.put(KEY_PARENT_MSG_ID, wrappedId);
+		} else {
+			throw new IllegalArgumentException(
+					"Unknown Message Type: " + type);
+		}
+		meta.put(KEY_ORIGINAL_MSG_ID, pOriginalHeader.getId());
+		meta.put(KEY_AUTHOR, authorToBdfDictionary(pOriginalHeader.getAuthor()));
+		meta.put(KEY_TIMESTAMP, pOriginalHeader.getTimestamp());
+		meta.put(KEY_TIME_RECEIVED, pOriginalHeader.getTimeReceived());
+
+		// Send wrapped message and store metadata
+		clientHelper.addLocalMessage(txn, wMessage, CLIENT_ID, meta, true);
+		return wMessage.getId();
+	}
+
 	@Override
 	public Blog getBlog(GroupId g) throws DbException {
 		Blog blog;
@@ -340,12 +499,13 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	}
 
 	@Override
-	public BlogPostHeader getPostHeader(MessageId m) throws DbException {
+	public BlogPostHeader getPostHeader(GroupId g, MessageId m)
+			throws DbException {
 		Transaction txn = db.startTransaction(true);
 		try {
 			BdfDictionary meta =
 					clientHelper.getMessageMetadataAsDictionary(txn, m);
-			BlogPostHeader h = getPostHeaderFromMetadata(txn, m, meta);
+			BlogPostHeader h = getPostHeaderFromMetadata(txn, g, m, meta);
 			txn.setComplete();
 			return h;
 		} catch (FormatException e) {
@@ -366,25 +526,50 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		}
 	}
 
+	// TODO directly return String (#598)
 	private byte[] getPostBody(BdfList message) throws FormatException {
-		// content, signature
-		// content: parent, contentType, title, body, attachments
-		BdfList content = message.getList(0);
-		return content.getRaw(3);
+		MessageType type = MessageType.valueOf(message.getLong(0).intValue());
+		if (type == POST) {
+			// type, body, signature
+			return StringUtils.toUtf8(message.getString(1));
+		} else if (type == WRAPPED_POST) {
+			// type, p_group descriptor, p_timestamp, p_content, p_signature
+			return StringUtils.toUtf8(message.getString(3));
+		} else {
+			throw new FormatException();
+		}
 	}
 
 	@Override
 	public Collection<BlogPostHeader> getPostHeaders(GroupId g)
 			throws DbException {
 
+		// Query for posts and comments only
+		BdfDictionary query1 = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, POST.getInt())
+		);
+		BdfDictionary query2 = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, COMMENT.getInt())
+		);
+
+		// TODO this could be optimized by looking up author status once (#625)
+
+		Collection<BlogPostHeader> headers = new ArrayList<BlogPostHeader>();
 		Transaction txn = db.startTransaction(true);
 		try {
+			Map<MessageId, BdfDictionary> metadata1 =
+					clientHelper.getMessageMetadataAsDictionary(txn, g, query1);
+			Map<MessageId, BdfDictionary> metadata2 =
+					clientHelper.getMessageMetadataAsDictionary(txn, g, query2);
 			Map<MessageId, BdfDictionary> metadata =
-					clientHelper.getMessageMetadataAsDictionary(txn, g);
-			List<BlogPostHeader> headers = new ArrayList<BlogPostHeader>();
+					new HashMap<MessageId, BdfDictionary>(
+							metadata1.size() + metadata2.size());
+			metadata.putAll(metadata1);
+			metadata.putAll(metadata2);
 			for (Entry<MessageId, BdfDictionary> entry : metadata.entrySet()) {
-				BlogPostHeader h = getPostHeaderFromMetadata(txn,
-						entry.getKey(), entry.getValue());
+				BdfDictionary meta = entry.getValue();
+				BlogPostHeader h =
+						getPostHeaderFromMetadata(txn, g, entry.getKey(), meta);
 				headers.add(h);
 			}
 			txn.setComplete();
@@ -419,10 +604,18 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 	}
 
 	private BlogPostHeader getPostHeaderFromMetadata(Transaction txn,
-			MessageId id, BdfDictionary meta)
+			GroupId groupId, MessageId id) throws DbException, FormatException {
+		BdfDictionary meta =
+				clientHelper.getMessageMetadataAsDictionary(txn, id);
+		return getPostHeaderFromMetadata(txn, groupId, id, meta);
+	}
+
+	private BlogPostHeader getPostHeaderFromMetadata(Transaction txn,
+			GroupId groupId, MessageId id, BdfDictionary meta)
 			throws DbException, FormatException {
 
-		String title = meta.getOptionalString(KEY_TITLE);
+		MessageType type = getMessageType(meta);
+
 		long timestamp = meta.getLong(KEY_TIMESTAMP);
 		long timeReceived = meta.getLong(KEY_TIME_RECEIVED, timestamp);
 
@@ -431,12 +624,35 @@ class BlogManagerImpl extends BdfIncomingMessageHook implements BlogManager,
 		String name = d.getString(KEY_AUTHOR_NAME);
 		byte[] publicKey = d.getRaw(KEY_PUBLIC_KEY);
 		Author author = new Author(authorId, name, publicKey);
-		Status authorStatus;
-		authorStatus = identityManager.getAuthorStatus(txn, authorId);
+		Status authorStatus = identityManager.getAuthorStatus(txn, authorId);
+
+		boolean read = meta.getBoolean(KEY_READ, false);
+
+		if (type == COMMENT || type == WRAPPED_COMMENT) {
+			String comment = meta.getOptionalString(KEY_COMMENT);
+			MessageId parentId = new MessageId(meta.getRaw(KEY_PARENT_MSG_ID));
+			BlogPostHeader parent =
+					getPostHeaderFromMetadata(txn, groupId, parentId);
+			return new BlogCommentHeader(type, groupId, comment, parent, id,
+					timestamp, timeReceived, author, authorStatus, read);
+		} else {
+			return new BlogPostHeader(type, groupId, id, timestamp,
+					timeReceived, author, authorStatus, read);
+		}
+	}
 
-		String contentType = meta.getString(KEY_CONTENT_TYPE);
-		boolean read = meta.getBoolean(KEY_READ);
-		return new BlogPostHeader(title, id, timestamp, timeReceived, author,
-				authorStatus, contentType, read);
+	private MessageType getMessageType(BdfDictionary d) throws FormatException {
+		Long longType = d.getLong(KEY_TYPE);
+		return MessageType.valueOf(longType.intValue());
+	}
+
+	// TODO remove when implementing #589
+	@Deprecated
+	private void share(Transaction txn, BlogPostHeader h) throws DbException {
+		clientHelper.setMessageShared(txn, h.getId(), true);
+		if (h instanceof BlogCommentHeader) {
+			BlogPostHeader h2 = ((BlogCommentHeader) h).getParent();
+			share(txn, h2);
+		}
 	}
 }
diff --git a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
index ce134d33c6214368f592037a545a605266f1ec88..62e6bc0addaecdd2e868bd780d68205f54f08e36 100644
--- a/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
+++ b/briar-core/src/org/briarproject/blogs/BlogPostFactoryImpl.java
@@ -3,17 +3,14 @@ package org.briarproject.blogs;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.blogs.BlogPost;
 import org.briarproject.api.blogs.BlogPostFactory;
+import org.briarproject.api.blogs.MessageType;
 import org.briarproject.api.clients.ClientHelper;
-import org.briarproject.api.crypto.CryptoComponent;
-import org.briarproject.api.crypto.KeyParser;
-import org.briarproject.api.crypto.PrivateKey;
-import org.briarproject.api.crypto.Signature;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.sync.GroupId;
 import org.briarproject.api.sync.Message;
 import org.briarproject.api.sync.MessageId;
-import org.briarproject.util.StringUtils;
+import org.briarproject.api.system.Clock;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -22,52 +19,143 @@ import java.security.GeneralSecurityException;
 import javax.inject.Inject;
 
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
-import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
-import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
+import static org.briarproject.api.blogs.MessageType.COMMENT;
+import static org.briarproject.api.blogs.MessageType.POST;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
+import static org.briarproject.api.blogs.MessageType.WRAPPED_POST;
 
 class BlogPostFactoryImpl implements BlogPostFactory {
 
-	private final CryptoComponent crypto;
 	private final ClientHelper clientHelper;
+	private final Clock clock;
 
 	@Inject
-	BlogPostFactoryImpl(CryptoComponent crypto, ClientHelper clientHelper) {
-		this.crypto = crypto;
+	BlogPostFactoryImpl(ClientHelper clientHelper, Clock clock) {
 		this.clientHelper = clientHelper;
+		this.clock = clock;
 	}
 
 	@Override
-	public BlogPost createBlogPost(@NotNull GroupId groupId,
-			@Nullable String title, long timestamp,
-			@Nullable MessageId parent,	@NotNull LocalAuthor author,
-			@NotNull String contentType, @NotNull byte[] body)
+	public BlogPost createBlogPost(@NotNull GroupId groupId, long timestamp,
+			@Nullable MessageId parent, @NotNull LocalAuthor author,
+			@NotNull String body)
 			throws FormatException, GeneralSecurityException {
 
 		// Validate the arguments
-		if (title != null &&
-				StringUtils.toUtf8(title).length > MAX_BLOG_POST_TITLE_LENGTH)
-			throw new IllegalArgumentException();
-		if (StringUtils.toUtf8(contentType).length > MAX_CONTENT_TYPE_LENGTH)
-			throw new IllegalArgumentException();
-		if (body.length > MAX_BLOG_POST_BODY_LENGTH)
+		if (body.length() > MAX_BLOG_POST_BODY_LENGTH)
 			throw new IllegalArgumentException();
 
 		// Serialise the data to be signed
-		BdfList content = BdfList.of(parent, contentType, title, body, null);
-		BdfList signed = BdfList.of(groupId, timestamp, content);
+		BdfList signed = BdfList.of(groupId, timestamp, body);
 
 		// Generate the signature
-		Signature signature = crypto.getSignature();
-		KeyParser keyParser = crypto.getSignatureKeyParser();
-		PrivateKey privateKey =
-				keyParser.parsePrivateKey(author.getPrivateKey());
-		signature.initSign(privateKey);
-		signature.update(clientHelper.toByteArray(signed));
-		byte[] sig = signature.sign();
+		byte[] sig = clientHelper.sign(signed, author.getPrivateKey());
 
 		// Serialise the signed message
-		BdfList message = BdfList.of(content, sig);
+		BdfList message = BdfList.of(POST.getInt(), body, sig);
 		Message m = clientHelper.createMessage(groupId, timestamp, message);
-		return new BlogPost(title, m, parent, author, contentType);
+		return new BlogPost(m, parent, author);
+	}
+
+	@Override
+	public Message createBlogComment(GroupId groupId, LocalAuthor author,
+			@Nullable String comment, MessageId pOriginalId, MessageId parentId)
+			throws FormatException, GeneralSecurityException {
+
+		long timestamp = clock.currentTimeMillis();
+
+		// Generate the signature
+		BdfList signed =
+				BdfList.of(groupId, timestamp, comment, pOriginalId, parentId);
+		byte[] sig = clientHelper.sign(signed, author.getPrivateKey());
+
+		// Serialise the signed message
+		BdfList message =
+				BdfList.of(COMMENT.getInt(), comment, pOriginalId, parentId,
+						sig);
+		return clientHelper.createMessage(groupId, timestamp, message);
+	}
+
+	@Override
+	public Message wrapPost(GroupId groupId, byte[] descriptor,
+			long timestamp, BdfList body)
+			throws FormatException {
+
+		if (getType(body) != POST)
+			throw new IllegalArgumentException("Needs to wrap a POST");
+
+		// Serialise the message
+		String content = body.getString(1);
+		byte[] signature = body.getRaw(2);
+		BdfList message =
+				BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp,
+						content, signature);
+		return clientHelper
+				.createMessage(groupId, clock.currentTimeMillis(), message);
+	}
+
+	@Override
+	public Message rewrapWrappedPost(GroupId groupId, BdfList body)
+			throws FormatException {
+
+		if (getType(body) != WRAPPED_POST)
+			throw new IllegalArgumentException("Needs to wrap a WRAPPED_POST");
+
+		// Serialise the message
+		byte[] descriptor = body.getRaw(1);
+		long timestamp = body.getLong(2);
+		String content = body.getString(3);
+		byte[] signature = body.getRaw(4);
+		BdfList message =
+				BdfList.of(WRAPPED_POST.getInt(), descriptor, timestamp,
+						content, signature);
+		return clientHelper
+				.createMessage(groupId, clock.currentTimeMillis(), message);
+	}
+
+	@Override
+	public Message wrapComment(GroupId groupId, byte[] descriptor,
+			long timestamp, BdfList body, MessageId parentId)
+			throws FormatException {
+
+		if (getType(body) != COMMENT)
+			throw new IllegalArgumentException("Needs to wrap a COMMENT");
+
+		// Serialise the message
+		String comment = body.getOptionalString(1);
+		byte[] pOriginalId = body.getRaw(2);
+		byte[] oldParentId = body.getRaw(3);
+		byte[] signature = body.getRaw(4);
+		BdfList message =
+				BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp,
+						comment, pOriginalId, oldParentId, signature, parentId);
+		return clientHelper
+				.createMessage(groupId, clock.currentTimeMillis(), message);
+	}
+
+	@Override
+	public Message rewrapWrappedComment(GroupId groupId, BdfList body,
+			MessageId parentId) throws FormatException {
+
+		if (getType(body) != WRAPPED_COMMENT)
+			throw new IllegalArgumentException(
+					"Needs to wrap a WRAPPED_COMMENT");
+
+		// Serialise the message
+		byte[] descriptor = body.getRaw(1);
+		long timestamp = body.getLong(2);
+		String comment = body.getOptionalString(3);
+		byte[] pOriginalId = body.getRaw(4);
+		byte[] oldParentId = body.getRaw(5);
+		byte[] signature = body.getRaw(6);
+		BdfList message =
+				BdfList.of(WRAPPED_COMMENT.getInt(), descriptor, timestamp,
+						comment, pOriginalId, oldParentId, signature, parentId);
+		return clientHelper
+				.createMessage(groupId, clock.currentTimeMillis(), message);
+	}
+
+	private MessageType getType(BdfList body) throws FormatException {
+		return MessageType.valueOf(body.getLong(0).intValue());
 	}
 }
diff --git a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
index 15b74adbe4f36d3b980dfedb5b62f1d16f3f92d9..eaab2313fe3ba592f6fc64bd6eb911a75373d446 100644
--- a/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
+++ b/briar-core/src/org/briarproject/blogs/BlogPostValidator.java
@@ -1,7 +1,6 @@
 package org.briarproject.blogs;
 
 import org.briarproject.api.FormatException;
-import org.briarproject.api.UniqueId;
 import org.briarproject.api.blogs.Blog;
 import org.briarproject.api.blogs.BlogFactory;
 import org.briarproject.api.blogs.MessageType;
@@ -33,18 +32,17 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
 import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
-import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
-import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_MSG_ID;
+import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_PARENT_MSG_ID;
+import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
 import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
-import static org.briarproject.api.blogs.BlogConstants.KEY_TITLE;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
 import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
-import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_TITLE_LENGTH;
-import static org.briarproject.api.blogs.BlogConstants.MAX_CONTENT_TYPE_LENGTH;
+import static org.briarproject.api.blogs.MessageType.COMMENT;
+import static org.briarproject.api.blogs.MessageType.POST;
 import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 
 class BlogPostValidator extends BdfMessageValidator {
@@ -72,13 +70,6 @@ class BlogPostValidator extends BdfMessageValidator {
 
 		BdfMessageContext c;
 
-		// TODO Remove! For Temporary Backwards Compatibility only!
-		if (body.get(0) instanceof BdfList) {
-			c = validatePost(m, g, body);
-			addMessageMetadata(c, m.getTimestamp());
-			return c;
-		}
-
 		int type = body.getLong(0).intValue();
 		body.removeElementAt(0);
 		switch (MessageType.valueOf(type)) {
@@ -108,83 +99,64 @@ class BlogPostValidator extends BdfMessageValidator {
 
 		// Content, Signature
 		checkSize(body, 2);
-		BdfList content = body.getList(0);
-
-		// Content: content type, title (optional), post body,
-		//          attachments (optional)
-		checkSize(content, 5);
-		// Parent ID is optional
-		// TODO remove when breaking backwards compatibility
-		byte[] parent = content.getOptionalRaw(0);
-		checkLength(parent, UniqueId.LENGTH);
-		// Content type
-		String contentType = content.getString(1);
-		checkLength(contentType, 0, MAX_CONTENT_TYPE_LENGTH);
-		if (!contentType.equals("text/plain"))
-			throw new InvalidMessageException("Invalid content type");
-		// Blog post title is optional
-		String title = content.getOptionalString(2);
-		checkLength(contentType, 0, MAX_BLOG_POST_TITLE_LENGTH);
-		// Blog post body
-		byte[] postBody = content.getRaw(3);
+		String postBody = body.getString(0);
 		checkLength(postBody, 0, MAX_BLOG_POST_BODY_LENGTH);
-		// Attachments
-		content.getOptionalDictionary(4);
 
 		// Verify Signature
 		byte[] sig = body.getRaw(1);
 		checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
-		BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), content);
+		BdfList signed = BdfList.of(g.getId(), m.getTimestamp(), postBody);
 		Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
 		Author a = b.getAuthor();
 		verifySignature(sig, a.getPublicKey(), signed);
 
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
-		if (title != null) meta.put(KEY_TITLE, title);
+		meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
 		meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
-		meta.put(KEY_CONTENT_TYPE, contentType);
 		return new BdfMessageContext(meta, null);
 	}
 
 	private BdfMessageContext validateComment(Message m, Group g, BdfList body)
 			throws InvalidMessageException, FormatException {
 
-		// comment, parent_original_id, signature, parent_current_id
+		// comment, parent_original_id, parent_id, signature
 		checkSize(body, 4);
 
 		// Comment
 		String comment = body.getOptionalString(0);
-		checkLength(comment, 0, MAX_BLOG_POST_BODY_LENGTH);
+		checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH);
 
 		// parent_original_id
 		// The ID of a post or comment in this group or another group
-		byte[] originalIdBytes = body.getRaw(1);
-		checkLength(originalIdBytes, MessageId.LENGTH);
-		MessageId originalId = new MessageId(originalIdBytes);
+		byte[] pOriginalIdBytes = body.getRaw(1);
+		checkLength(pOriginalIdBytes, MessageId.LENGTH);
+		MessageId pOriginalId = new MessageId(pOriginalIdBytes);
+
+		// parent_id
+		// The ID of a post, comment, wrapped post or wrapped comment in this
+		// group, which had the ID parent_original_id in the group
+		// where it was originally posted
+		byte[] currentIdBytes = body.getRaw(2);
+		checkLength(currentIdBytes, MessageId.LENGTH);
+		MessageId currentId = new MessageId(currentIdBytes);
 
 		// Signature
-		byte[] sig = body.getRaw(2);
+		byte[] sig = body.getRaw(3);
 		checkLength(sig, 0, MAX_SIGNATURE_LENGTH);
 		BdfList signed =
-				BdfList.of(g.getId(), m.getTimestamp(), comment, originalId);
+				BdfList.of(g.getId(), m.getTimestamp(), comment, pOriginalId,
+						currentId);
 		Blog b = blogFactory.parseBlog(g, ""); // description doesn't matter
 		Author a = b.getAuthor();
 		verifySignature(sig, a.getPublicKey(), signed);
 
-		// parent_current_id
-		// The ID of a post, comment, wrapped post or wrapped comment in this
-		// group, which had the ID parent_original_id in the group
-		// where it was originally posted
-		byte[] currentIdBytes = body.getRaw(3);
-		checkLength(currentIdBytes, MessageId.LENGTH);
-		MessageId currentId = new MessageId(currentIdBytes);
-
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
 		if (comment != null) meta.put(KEY_COMMENT, comment);
-		meta.put(KEY_ORIGINAL_MSG_ID, originalId);
-		meta.put(KEY_CURRENT_MSG_ID, currentId);
+		meta.put(KEY_ORIGINAL_MSG_ID, m.getId());
+		meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
+		meta.put(KEY_PARENT_MSG_ID, currentId);
 		meta.put(KEY_AUTHOR, authorToBdfDictionary(a));
 		Collection<MessageId> dependencies = Collections.singleton(currentId);
 		return new BdfMessageContext(meta, dependencies);
@@ -193,7 +165,7 @@ class BlogPostValidator extends BdfMessageValidator {
 	private BdfMessageContext validateWrappedPost(Message m, Group g,
 			BdfList body) throws InvalidMessageException, FormatException {
 
-		// group descriptor, timestamp, content, signature
+		// p_group descriptor, p_timestamp, p_content, p_signature
 		checkSize(body, 4);
 
 		// Group Descriptor
@@ -201,9 +173,10 @@ class BlogPostValidator extends BdfMessageValidator {
 
 		// Timestamp of Wrapped Post
 		long wTimestamp = body.getLong(1);
+		if (wTimestamp < 0) throw new FormatException();
 
 		// Content of Wrapped Post
-		BdfList content = body.getList(2);
+		String content = body.getString(2);
 
 		// Signature of Wrapped Post
 		byte[] signature = body.getRaw(3);
@@ -212,71 +185,81 @@ class BlogPostValidator extends BdfMessageValidator {
 		// Get and Validate the Wrapped Message
 		Group wGroup = groupFactory
 				.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
-		BdfList wBodyList = BdfList.of(content, signature);
+		BdfList wBodyList = BdfList.of(POST.getInt(), content, signature);
 		byte[] wBody = clientHelper.toByteArray(wBodyList);
 		Message wMessage =
 				messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody);
+		wBodyList.remove(0);
 		BdfMessageContext c = validatePost(wMessage, wGroup, wBodyList);
 
 		// Return the metadata and dependencies
 		BdfDictionary meta = new BdfDictionary();
+		meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
 		meta.put(KEY_TIMESTAMP, wTimestamp);
 		meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
-		meta.put(KEY_CONTENT_TYPE,
-				c.getDictionary().getString(KEY_CONTENT_TYPE));
 		return new BdfMessageContext(meta, null);
 	}
 
 	private BdfMessageContext validateWrappedComment(Message m, Group g,
 			BdfList body) throws InvalidMessageException, FormatException {
 
-		// group descriptor, timestamp, comment, parent_original_id, signature,
-		// parent_current_id
-		checkSize(body, 6);
+		// c_group descriptor, c_timestamp, c_comment, c_parent_original_id,
+		// c_parent_id, c_signature, parent_id
+		checkSize(body, 7);
 
 		// Group Descriptor
 		byte[] descriptor = body.getRaw(0);
 
 		// Timestamp of Wrapped Comment
 		long wTimestamp = body.getLong(1);
+		if (wTimestamp < 0) throw new FormatException();
 
 		// Body of Wrapped Comment
 		String comment = body.getOptionalString(2);
+		checkLength(comment, 1, MAX_BLOG_POST_BODY_LENGTH);
 
-		// parent_original_id
+		// c_parent_original_id
 		// Taken from the original comment
-		byte[] originalIdBytes = body.getRaw(3);
-		checkLength(originalIdBytes, MessageId.LENGTH);
-		MessageId originalId = new MessageId(originalIdBytes);
+		byte[] pOriginalIdBytes = body.getRaw(3);
+		checkLength(pOriginalIdBytes, MessageId.LENGTH);
+		MessageId pOriginalId = new MessageId(pOriginalIdBytes);
 
-		// signature
+		// c_parent_id
 		// Taken from the original comment
-		byte[] signature = body.getRaw(4);
+		byte[] oldIdBytes = body.getRaw(4);
+		checkLength(oldIdBytes, MessageId.LENGTH);
+		MessageId oldId = new MessageId(oldIdBytes);
+
+		// c_signature
+		// Taken from the original comment
+		byte[] signature = body.getRaw(5);
 		checkLength(signature, 1, MAX_SIGNATURE_LENGTH);
 
-		// parent_current_id
+		// parent_id
 		// The ID of a post, comment, wrapped post or wrapped comment in this
-		// group, which had the ID parent_original_id in the group
+		// group, which had the ID c_parent_original_id in the group
 		// where it was originally posted
-		byte[] currentIdBytes = body.getRaw(5);
-		checkLength(currentIdBytes, MessageId.LENGTH);
-		MessageId currentId = new MessageId(currentIdBytes);
+		byte[] parentIdBytes = body.getRaw(6);
+		checkLength(parentIdBytes, MessageId.LENGTH);
+		MessageId parentId = new MessageId(parentIdBytes);
 
 		// Get and Validate the Wrapped Comment
 		Group wGroup = groupFactory
 				.createGroup(BlogManagerImpl.CLIENT_ID, descriptor);
-		BdfList wBodyList =	BdfList.of(comment, originalId, signature,
-				currentId);
+		BdfList wBodyList =	BdfList.of(COMMENT.getInt(), comment, pOriginalId,
+				oldId, signature);
 		byte[] wBody = clientHelper.toByteArray(wBodyList);
 		Message wMessage =
 				messageFactory.createMessage(wGroup.getId(), wTimestamp, wBody);
+		wBodyList.remove(0);
 		BdfMessageContext c = validateComment(wMessage, wGroup, wBodyList);
 
 		// Return the metadata and dependencies
-		Collection<MessageId> dependencies = Collections.singleton(currentId);
+		Collection<MessageId> dependencies = Collections.singleton(parentId);
 		BdfDictionary meta = new BdfDictionary();
 		meta.put(KEY_ORIGINAL_MSG_ID, wMessage.getId());
-		meta.put(KEY_CURRENT_MSG_ID, currentId);
+		meta.put(KEY_ORIGINAL_PARENT_MSG_ID, pOriginalId);
+		meta.put(KEY_PARENT_MSG_ID, parentId);
 		meta.put(KEY_TIMESTAMP, wTimestamp);
 		if (comment != null) meta.put(KEY_COMMENT, comment);
 		meta.put(KEY_AUTHOR, c.getDictionary().getDictionary(KEY_AUTHOR));
diff --git a/briar-core/src/org/briarproject/blogs/BlogsModule.java b/briar-core/src/org/briarproject/blogs/BlogsModule.java
index a93d39aa4534f14cd5f56807776432677426a87d..91cc68c7c5ff2d5052367aff744d16ace49a2293 100644
--- a/briar-core/src/org/briarproject/blogs/BlogsModule.java
+++ b/briar-core/src/org/briarproject/blogs/BlogsModule.java
@@ -50,9 +50,9 @@ public class BlogsModule {
 	}
 
 	@Provides
-	BlogPostFactory provideBlogPostFactory(CryptoComponent crypto,
-			ClientHelper clientHelper) {
-		return new BlogPostFactoryImpl(crypto, clientHelper);
+	BlogPostFactory provideBlogPostFactory(ClientHelper clientHelper,
+			Clock clock) {
+		return new BlogPostFactoryImpl(clientHelper, clock);
 	}
 
 	@Provides
diff --git a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java
index 00778b6b6dab5391c14bf6eff58db8a49ca6f66c..a8af6137ab93a3bd191c8d92a9fb976f15a9264f 100644
--- a/briar-core/src/org/briarproject/clients/ClientHelperImpl.java
+++ b/briar-core/src/org/briarproject/clients/ClientHelperImpl.java
@@ -2,6 +2,10 @@ package org.briarproject.clients;
 
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.KeyParser;
+import org.briarproject.api.crypto.PrivateKey;
+import org.briarproject.api.crypto.Signature;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.BdfReader;
@@ -23,6 +27,7 @@ import org.briarproject.api.sync.MessageId;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.security.GeneralSecurityException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
@@ -40,18 +45,20 @@ class ClientHelperImpl implements ClientHelper {
 	private final BdfWriterFactory bdfWriterFactory;
 	private final MetadataParser metadataParser;
 	private final MetadataEncoder metadataEncoder;
+	private final CryptoComponent cryptoComponent;
 
 	@Inject
 	ClientHelperImpl(DatabaseComponent db, MessageFactory messageFactory,
 			BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
-			MetadataEncoder metadataEncoder) {
+			MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
 		this.db = db;
 		this.messageFactory = messageFactory;
 		this.bdfReaderFactory = bdfReaderFactory;
 		this.bdfWriterFactory = bdfWriterFactory;
 		this.metadataParser = metadataParser;
 		this.metadataEncoder = metadataEncoder;
+		this.cryptoComponent = cryptoComponent;
 	}
 
 	@Override
@@ -240,7 +247,7 @@ class ClientHelperImpl implements ClientHelper {
 	}
 
 	@Override
-	public void setMessageShared(Transaction txn, Message m, boolean shared)
+	public void setMessageShared(Transaction txn, MessageId m, boolean shared)
 			throws DbException {
 		db.setMessageShared(txn, m, shared);
 	}
@@ -303,4 +310,21 @@ class ClientHelperImpl implements ClientHelper {
 			throw new RuntimeException(e);
 		}
 	}
+
+	@Override
+	public BdfList toList(byte[] b) throws FormatException {
+		return toList(b, 0, b.length);
+	}
+
+	@Override
+	public byte[] sign(BdfList toSign, byte[] privateKey)
+			throws FormatException, GeneralSecurityException {
+		Signature signature = cryptoComponent.getSignature();
+		KeyParser keyParser = cryptoComponent.getSignatureKeyParser();
+		PrivateKey key =
+				keyParser.parsePrivateKey(privateKey);
+		signature.initSign(key);
+		signature.update(toByteArray(toSign));
+		return signature.sign();
+	}
 }
diff --git a/briar-core/src/org/briarproject/clients/ClientsModule.java b/briar-core/src/org/briarproject/clients/ClientsModule.java
index 302939ff3047143e9828d96f9f6d29e41761bacb..1262632c07ade0ade99c6cbac9e2830d4f6b72b1 100644
--- a/briar-core/src/org/briarproject/clients/ClientsModule.java
+++ b/briar-core/src/org/briarproject/clients/ClientsModule.java
@@ -26,9 +26,10 @@ public class ClientsModule {
 	ClientHelper provideClientHelper(DatabaseComponent db,
 			MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
 			BdfWriterFactory bdfWriterFactory, MetadataParser metadataParser,
-			MetadataEncoder metadataEncoder) {
+			MetadataEncoder metadataEncoder, CryptoComponent cryptoComponent) {
 		return new ClientHelperImpl(db, messageFactory, bdfReaderFactory,
-				bdfWriterFactory, metadataParser, metadataEncoder);
+				bdfWriterFactory, metadataParser, metadataEncoder,
+				cryptoComponent);
 	}
 
 	@Provides
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 1039a69e07bfa5467f48ac9084c29e15907afb47..01a2d9a96df25e5a22800ba37b888a66d729bf26 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -199,7 +199,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 			transaction.attach(new MessageAddedEvent(m, null));
 			transaction.attach(new MessageStateChangedEvent(m, c, true,
 					DELIVERED));
-			if (shared) transaction.attach(new MessageSharedEvent(m));
+			if (shared) transaction.attach(new MessageSharedEvent(m.getId()));
 		}
 		db.mergeMessageMetadata(txn, m.getId(), meta);
 	}
@@ -704,13 +704,13 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
 		transaction.attach(new ContactStatusChangedEvent(c, active));
 	}
 
-	public void setMessageShared(Transaction transaction, Message m,
+	public void setMessageShared(Transaction transaction, MessageId m,
 			boolean shared) throws DbException {
 		if (transaction.isReadOnly()) throw new IllegalArgumentException();
 		T txn = unbox(transaction);
-		if (!db.containsMessage(txn, m.getId()))
+		if (!db.containsMessage(txn, m))
 			throw new NoSuchMessageException();
-		db.setMessageShared(txn, m.getId(), shared);
+		db.setMessageShared(txn, m, shared);
 		if (shared) transaction.attach(new MessageSharedEvent(m));
 	}
 
diff --git a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
index 9379840da7fa8b18cd5c93a9881ef0ad47893609..344165b32f23ee4e0d1237f85fc34c84e1410362 100644
--- a/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
+++ b/briar-core/src/org/briarproject/feed/FeedManagerImpl.java
@@ -23,10 +23,10 @@ import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Transaction;
 import org.briarproject.api.feed.Feed;
 import org.briarproject.api.feed.FeedManager;
-import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.identity.AuthorId;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.lifecycle.IoExecutor;
 import org.briarproject.api.lifecycle.Service;
 import org.briarproject.api.lifecycle.ServiceException;
 import org.briarproject.api.sync.ClientId;
@@ -41,7 +41,6 @@ import java.net.InetSocketAddress;
 import java.net.Proxy;
 import java.security.GeneralSecurityException;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.Date;
@@ -440,15 +439,14 @@ class FeedManagerImpl implements FeedManager, Service, Client {
 		// get other information for post
 		GroupId groupId = feed.getBlogId();
 		long time = clock.currentTimeMillis();
-		byte[] body = getPostBody(b.toString());
+		String body = getPostBody(b.toString());
 		try {
 			// create and store post
 			Blog blog = blogManager.getBlog(txn, groupId);
 			AuthorId authorId = blog.getAuthor().getId();
 			LocalAuthor author = identityManager.getLocalAuthor(txn, authorId);
 			BlogPost post = blogPostFactory
-					.createBlogPost(groupId, null, time, null, author,
-							"text/plain", body);
+					.createBlogPost(groupId, time, null, author, body);
 			blogManager.addLocalPost(txn, post);
 		} catch (DbException e) {
 			if (LOG.isLoggable(WARNING))
@@ -472,10 +470,9 @@ class FeedManagerImpl implements FeedManager, Service, Client {
 		return StringUtils.trim(s.replaceAll("<(?s).*?>", ""));
 	}
 
-	private byte[] getPostBody(String text) {
-		byte[] body = StringUtils.toUtf8(text);
-		if (body.length <= MAX_BLOG_POST_BODY_LENGTH) return body;
-		else return Arrays.copyOfRange(body, 0, MAX_BLOG_POST_BODY_LENGTH - 1);
+	private String getPostBody(String text) {
+		if (text.length() <= MAX_BLOG_POST_BODY_LENGTH) return text;
+		else return text.substring(0, MAX_BLOG_POST_BODY_LENGTH);
 	}
 
 	/**
diff --git a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
index 84f28dbf995daf4099f25a8cbfba5f286859bde7..3e8b5de64eae11aa79330c5cf583341a789a982f 100644
--- a/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumManagerImpl.java
@@ -38,7 +38,6 @@ import java.util.logging.Logger;
 import javax.inject.Inject;
 
 import static org.briarproject.api.forum.ForumConstants.KEY_AUTHOR;
-import static org.briarproject.api.forum.ForumConstants.KEY_CONTENT_TYPE;
 import static org.briarproject.api.forum.ForumConstants.KEY_ID;
 import static org.briarproject.api.forum.ForumConstants.KEY_LOCAL;
 import static org.briarproject.api.forum.ForumConstants.KEY_NAME;
@@ -78,7 +77,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 	protected void incomingMessage(Transaction txn, Message m, BdfList body,
 			BdfDictionary meta) throws DbException, FormatException {
 
-		clientHelper.setMessageShared(txn, m, true);
+		clientHelper.setMessageShared(txn, m.getId(), true);
 
 		ForumPostHeader post = getForumPostHeader(txn, m.getId(), meta);
 		ForumPostReceivedEvent event =
@@ -132,7 +131,6 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 				authorMeta.put(KEY_PUBLIC_NAME, a.getPublicKey());
 				meta.put(KEY_AUTHOR, authorMeta);
 			}
-			meta.put(KEY_CONTENT_TYPE, p.getContentType());
 			meta.put(KEY_LOCAL, true);
 			meta.put(KEY_READ, true);
 			clientHelper.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true);
@@ -236,7 +234,7 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 	private Forum parseForum(Group g) throws FormatException {
 		byte[] descriptor = g.getDescriptor();
 		// Name, salt
-		BdfList forum = clientHelper.toList(descriptor, 0, descriptor.length);
+		BdfList forum = clientHelper.toList(descriptor);
 		return new Forum(g, forum.getString(0), forum.getRaw(1));
 	}
 
@@ -262,11 +260,10 @@ class ForumManagerImpl extends BdfIncomingMessageHook implements ForumManager {
 						identityManager.getAuthorStatus(txn, author.getId());
 			}
 		}
-		String contentType = meta.getString(KEY_CONTENT_TYPE);
 		boolean read = meta.getBoolean(KEY_READ);
 
 		return new ForumPostHeader(id, parentId, timestamp, author,
-				authorStatus, contentType, read);
+				authorStatus, read);
 	}
 
 	private ForumPostHeader getForumPostHeader(MessageId id,
diff --git a/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java b/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java
index c3c2067f19170e3a5b41cd515deab31ad5d82b54..a6c0b226aea4109724a72ebb36ae02ca6299a937 100644
--- a/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java
+++ b/briar-core/src/org/briarproject/forum/ForumPostFactoryImpl.java
@@ -44,7 +44,7 @@ class ForumPostFactoryImpl implements ForumPostFactory {
 		// Serialise the message
 		BdfList message = BdfList.of(parent, null, contentType, body, null);
 		Message m = clientHelper.createMessage(groupId, timestamp, message);
-		return new ForumPost(m, parent, null, contentType);
+		return new ForumPost(m, parent, null);
 	}
 
 	@Override
@@ -71,6 +71,6 @@ class ForumPostFactoryImpl implements ForumPostFactory {
 		BdfList message = BdfList.of(parent, authorList, contentType, body,
 				sig);
 		Message m = clientHelper.createMessage(groupId, timestamp, message);
-		return new ForumPost(m, parent, author, contentType);
+		return new ForumPost(m, parent, author);
 	}
 }
diff --git a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java
index 9171103303cd56591afb0ea78d1f3c4f4054d0a3..f2531a6c6b795ba95cf4af3b5bc185ebb21f28b5 100644
--- a/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java
+++ b/briar-tests/src/org/briarproject/blogs/BlogManagerImplTest.java
@@ -5,6 +5,7 @@ import org.briarproject.api.FormatException;
 import org.briarproject.api.blogs.Blog;
 import org.briarproject.api.blogs.BlogFactory;
 import org.briarproject.api.blogs.BlogPost;
+import org.briarproject.api.blogs.BlogPostFactory;
 import org.briarproject.api.blogs.BlogPostHeader;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.Contact;
@@ -33,17 +34,20 @@ import org.junit.Test;
 import java.util.Collection;
 import java.util.Collections;
 
+import javax.inject.Inject;
+
 import static org.briarproject.TestUtils.getRandomBytes;
 import static org.briarproject.TestUtils.getRandomId;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
-import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
 import static org.briarproject.api.blogs.BlogConstants.KEY_DESCRIPTION;
 import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
 import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIMESTAMP;
 import static org.briarproject.api.blogs.BlogConstants.KEY_TIME_RECEIVED;
+import static org.briarproject.api.blogs.BlogConstants.KEY_TYPE;
+import static org.briarproject.api.blogs.MessageType.POST;
 import static org.briarproject.api.identity.Author.Status.VERIFIED;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
 import static org.briarproject.blogs.BlogManagerImpl.CLIENT_ID;
@@ -68,9 +72,13 @@ public class BlogManagerImplTest extends BriarTestCase {
 	private final Message message;
 	private final MessageId messageId;
 
+	@Inject
+	@SuppressWarnings("WeakerAccess")
+	BlogPostFactory blogPostFactory;
+
 	public BlogManagerImplTest() {
 		blogManager = new BlogManagerImpl(db, identityManager, clientHelper,
-				metadataParser, contactManager, blogFactory);
+				metadataParser, contactManager, blogFactory, blogPostFactory);
 
 		blog1 = getBlog("Test Blog 1", "Test Description 1");
 		blog2 = getBlog("Test Blog 2", "Test Description 2");
@@ -183,16 +191,15 @@ public class BlogManagerImplTest extends BriarTestCase {
 		BdfList list = new BdfList();
 		BdfDictionary author = authorToBdfDictionary(blog1.getAuthor());
 		BdfDictionary meta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, POST.getInt()),
 				new BdfEntry(KEY_TIMESTAMP, 0),
 				new BdfEntry(KEY_TIME_RECEIVED, 1),
 				new BdfEntry(KEY_AUTHOR, author),
-				new BdfEntry(KEY_CONTENT_TYPE, 0),
-				new BdfEntry(KEY_READ, false),
-				new BdfEntry(KEY_CONTENT_TYPE, "text/plain")
+				new BdfEntry(KEY_READ, false)
 		);
 
 		context.checking(new Expectations() {{
-			oneOf(clientHelper).setMessageShared(txn, message, true);
+			oneOf(clientHelper).setMessageShared(txn, messageId, true);
 			oneOf(identityManager)
 					.getAuthorStatus(txn, blog1.getAuthor().getId());
 			will(returnValue(VERIFIED));
@@ -211,7 +218,6 @@ public class BlogManagerImplTest extends BriarTestCase {
 		assertEquals(messageId, h.getId());
 		assertEquals(null, h.getParentId());
 		assertEquals(VERIFIED, h.getAuthorStatus());
-		assertEquals("text/plain", h.getContentType());
 		assertEquals(blog1.getAuthor(), h.getAuthor());
 	}
 
@@ -273,13 +279,12 @@ public class BlogManagerImplTest extends BriarTestCase {
 	public void testAddLocalPost() throws DbException, FormatException {
 		final Transaction txn = new Transaction(null, true);
 		final BlogPost post =
-				new BlogPost(null, message, null, blog1.getAuthor(),
-						"text/plain");
+				new BlogPost(message, null, blog1.getAuthor());
 		BdfDictionary authorMeta = authorToBdfDictionary(blog1.getAuthor());
 		final BdfDictionary meta = BdfDictionary.of(
+				new BdfEntry(KEY_TYPE, POST.getInt()),
 				new BdfEntry(KEY_TIMESTAMP, message.getTimestamp()),
 				new BdfEntry(KEY_AUTHOR, authorMeta),
-				new BdfEntry(KEY_CONTENT_TYPE, "text/plain"),
 				new BdfEntry(KEY_READ, true)
 		);
 
@@ -308,7 +313,6 @@ public class BlogManagerImplTest extends BriarTestCase {
 		assertEquals(messageId, h.getId());
 		assertEquals(null, h.getParentId());
 		assertEquals(VERIFIED, h.getAuthorStatus());
-		assertEquals("text/plain", h.getContentType());
 		assertEquals(blog1.getAuthor(), h.getAuthor());
 	}
 
diff --git a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java
index e64f29ba6b65ce2ad4fb262d80d1e449a04d5ff3..fae1f04d7b7847ac88098d69c3fcf690011faeb2 100644
--- a/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java
+++ b/briar-tests/src/org/briarproject/blogs/BlogPostValidatorTest.java
@@ -37,12 +37,10 @@ import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_AUTHOR_NAME;
 import static org.briarproject.api.blogs.BlogConstants.KEY_COMMENT;
-import static org.briarproject.api.blogs.BlogConstants.KEY_CONTENT_TYPE;
-import static org.briarproject.api.blogs.BlogConstants.KEY_CURRENT_MSG_ID;
+import static org.briarproject.api.blogs.BlogConstants.KEY_PARENT_MSG_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_ORIGINAL_MSG_ID;
 import static org.briarproject.api.blogs.BlogConstants.KEY_PUBLIC_KEY;
 import static org.briarproject.api.blogs.BlogConstants.KEY_READ;
-import static org.briarproject.api.blogs.BlogConstants.MAX_BLOG_POST_BODY_LENGTH;
 import static org.briarproject.api.blogs.MessageType.COMMENT;
 import static org.briarproject.api.blogs.MessageType.POST;
 import static org.briarproject.api.blogs.MessageType.WRAPPED_COMMENT;
@@ -70,8 +68,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	private final BlogFactory blogFactory = context.mock(BlogFactory.class);
 	private final ClientHelper clientHelper = context.mock(ClientHelper.class);
 	private final Clock clock = new SystemClock();
-	private final byte[] body = TestUtils.getRandomBytes(
-			MAX_BLOG_POST_BODY_LENGTH);
+	private final String body = TestUtils.getRandomString(42);
 	private final String contentType = "text/plain";
 
 	public BlogPostValidatorTest() {
@@ -105,18 +102,15 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	@Test
 	public void testValidateProperBlogPost()
 			throws IOException, GeneralSecurityException {
-		// content type, title (optional), post body, attachments
-		BdfList content = BdfList.of(null, contentType, null, body, null);
 		final byte[] sigBytes = TestUtils.getRandomBytes(42);
-		BdfList m = BdfList.of(POST.getInt(), content, sigBytes);
+		BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
 
 		BdfList signed =
-				BdfList.of(blog.getId(), message.getTimestamp(), content);
+				BdfList.of(blog.getId(), message.getTimestamp(), body);
 		expectCrypto(signed, sigBytes, true);
 		final BdfDictionary result =
 				validator.validateMessage(message, group, m).getDictionary();
 
-		assertEquals(contentType, result.getString(KEY_CONTENT_TYPE));
 		assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
 		assertFalse(result.getBoolean(KEY_READ));
 		context.assertIsSatisfied();
@@ -143,13 +137,11 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	@Test(expected = InvalidMessageException.class)
 	public void testValidateBlogPostWithBadSignature()
 			throws IOException, GeneralSecurityException {
-		// content type, title (optional), post body, attachments
-		BdfList content = BdfList.of(null, contentType, null, body, null);
 		final byte[] sigBytes = TestUtils.getRandomBytes(42);
-		BdfList m = BdfList.of(POST.getInt(), content, sigBytes);
+		BdfList m = BdfList.of(POST.getInt(), body, sigBytes);
 
 		BdfList signed =
-				BdfList.of(blog.getId(), message.getTimestamp(), content);
+				BdfList.of(blog.getId(), message.getTimestamp(), body);
 		expectCrypto(signed, sigBytes, false);
 		validator.validateMessage(message, group, m).getDictionary();
 	}
@@ -157,17 +149,18 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	@Test
 	public void testValidateProperBlogComment()
 			throws IOException, GeneralSecurityException {
-		// comment, parent_original_id, signature, parent_current_id
+		// comment, parent_original_id, parent_id, signature
 		String comment = "This is a blog comment";
 		MessageId originalId = new MessageId(TestUtils.getRandomId());
-		byte[] currentId = TestUtils.getRandomId();
+		MessageId currentId = new MessageId(TestUtils.getRandomId());
 		final byte[] sigBytes = TestUtils.getRandomBytes(42);
-		BdfList m = BdfList.of(COMMENT.getInt(), comment, originalId,
-				sigBytes, currentId);
+		BdfList m =
+				BdfList.of(COMMENT.getInt(), comment, originalId, currentId,
+						sigBytes);
 
 		BdfList signed =
 				BdfList.of(blog.getId(), message.getTimestamp(), comment,
-						originalId);
+						originalId, currentId);
 		expectCrypto(signed, sigBytes, true);
 		final BdfDictionary result =
 				validator.validateMessage(message, group, m).getDictionary();
@@ -175,7 +168,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 		assertEquals(comment, result.getString(KEY_COMMENT));
 		assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
 		assertEquals(originalId.getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
-		assertEquals(currentId, result.getRaw(KEY_CURRENT_MSG_ID));
+		assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
 		assertFalse(result.getBoolean(KEY_READ));
 		context.assertIsSatisfied();
 	}
@@ -185,14 +178,15 @@ public class BlogPostValidatorTest extends BriarTestCase {
 			throws IOException, GeneralSecurityException {
 		// comment, parent_original_id, signature, parent_current_id
 		MessageId originalId = new MessageId(TestUtils.getRandomId());
-		byte[] currentId = TestUtils.getRandomId();
+		MessageId currentId = new MessageId(TestUtils.getRandomId());
 		final byte[] sigBytes = TestUtils.getRandomBytes(42);
-		BdfList m = BdfList.of(COMMENT.getInt(), null, originalId, sigBytes,
-				currentId);
+		BdfList m =
+				BdfList.of(COMMENT.getInt(), null, originalId, currentId,
+						sigBytes);
 
 		BdfList signed =
 				BdfList.of(blog.getId(), message.getTimestamp(), null,
-						originalId);
+						originalId, currentId);
 		expectCrypto(signed, sigBytes, true);
 		final BdfDictionary result =
 				validator.validateMessage(message, group, m).getDictionary();
@@ -205,17 +199,16 @@ public class BlogPostValidatorTest extends BriarTestCase {
 	public void testValidateProperWrappedPost()
 			throws IOException, GeneralSecurityException {
 		// group descriptor, timestamp, content, signature
-		BdfList content = BdfList.of(null, contentType, null, body, null);
 		final byte[] sigBytes = TestUtils.getRandomBytes(42);
 		BdfList m =
 				BdfList.of(WRAPPED_POST.getInt(), descriptor,
-						message.getTimestamp(), content, sigBytes);
+						message.getTimestamp(), body, sigBytes);
 
 		BdfList signed =
-				BdfList.of(blog.getId(), message.getTimestamp(), content);
+				BdfList.of(blog.getId(), message.getTimestamp(), body);
 		expectCrypto(signed, sigBytes, true);
 
-		final BdfList originalList = BdfList.of(content, sigBytes);
+		final BdfList originalList = BdfList.of(POST.getInt(), body, sigBytes);
 		final byte[] originalBody = TestUtils.getRandomBytes(42);
 
 		context.checking(new Expectations() {{
@@ -232,7 +225,6 @@ public class BlogPostValidatorTest extends BriarTestCase {
 		final BdfDictionary result =
 				validator.validateMessage(message, group, m).getDictionary();
 
-		assertEquals(contentType, result.getString(KEY_CONTENT_TYPE));
 		assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
 		context.assertIsSatisfied();
 	}
@@ -244,18 +236,19 @@ public class BlogPostValidatorTest extends BriarTestCase {
 		// parent_current_id
 		String comment = "This is another comment";
 		MessageId originalId = new MessageId(TestUtils.getRandomId());
+		MessageId oldId = new MessageId(TestUtils.getRandomId());
 		final byte[] sigBytes = TestUtils.getRandomBytes(42);
 		MessageId currentId = new MessageId(TestUtils.getRandomId());
 		BdfList m = BdfList.of(WRAPPED_COMMENT.getInt(), descriptor,
-				message.getTimestamp(), comment, originalId, sigBytes,
+				message.getTimestamp(), comment, originalId, oldId, sigBytes,
 				currentId);
 
 		BdfList signed = BdfList.of(blog.getId(), message.getTimestamp(),
-				comment, originalId);
+				comment, originalId, oldId);
 		expectCrypto(signed, sigBytes, true);
 
-		final BdfList originalList = BdfList.of(comment, originalId, sigBytes,
-				currentId);
+		final BdfList originalList = BdfList.of(COMMENT.getInt(), comment,
+				originalId, oldId, sigBytes);
 		final byte[] originalBody = TestUtils.getRandomBytes(42);
 
 		context.checking(new Expectations() {{
@@ -276,7 +269,7 @@ public class BlogPostValidatorTest extends BriarTestCase {
 		assertEquals(authorDict, result.getDictionary(KEY_AUTHOR));
 		assertEquals(
 				message.getId().getBytes(), result.getRaw(KEY_ORIGINAL_MSG_ID));
-		assertEquals(currentId.getBytes(), result.getRaw(KEY_CURRENT_MSG_ID));
+		assertEquals(currentId.getBytes(), result.getRaw(KEY_PARENT_MSG_ID));
 		context.assertIsSatisfied();
 	}
 
diff --git a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
index 30f46c03280ba350a99468dac35dd576a6b75be7..123ca114c0cb89539a704087d043f89eca2fe527 100644
--- a/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
+++ b/briar-tests/src/org/briarproject/db/DatabaseComponentImplTest.java
@@ -751,7 +751,7 @@ public class DatabaseComponentImplTest extends BriarTestCase {
 
 		transaction = db.startTransaction(false);
 		try {
-			db.setMessageShared(transaction, message, true);
+			db.setMessageShared(transaction, message.getId(), true);
 			fail();
 		} catch (NoSuchMessageException expected) {
 			// Expected