diff --git a/api/net/sf/briar/api/protocol/Offer.java b/api/net/sf/briar/api/protocol/Offer.java
index e62f05d56aac13afa2bcf3863f6340f665d8512f..fbeda85080afe92ed2667dcd94cc89da3d208fea 100644
--- a/api/net/sf/briar/api/protocol/Offer.java
+++ b/api/net/sf/briar/api/protocol/Offer.java
@@ -11,6 +11,9 @@ public interface Offer {
 	 */
 	static final int MAX_SIZE = (1024 * 1024) - 100;
 
+	/** Returns the offer's unique identifier. */
+	OfferId getId();
+
 	/** Returns the message IDs contained in the offer. */
 	Collection<MessageId> getMessageIds();
 }
diff --git a/api/net/sf/briar/api/protocol/OfferId.java b/api/net/sf/briar/api/protocol/OfferId.java
new file mode 100644
index 0000000000000000000000000000000000000000..1d235cfe71815f2d49c53b4e59d0cf54ca2866c7
--- /dev/null
+++ b/api/net/sf/briar/api/protocol/OfferId.java
@@ -0,0 +1,26 @@
+package net.sf.briar.api.protocol;
+
+import java.io.IOException;
+import java.util.Arrays;
+
+import net.sf.briar.api.serial.Writer;
+
+/** Type-safe wrapper for a byte array that uniquely identifies an offer. */
+public class OfferId extends UniqueId {
+
+	public OfferId(byte[] id) {
+		super(id);
+	}
+
+	public void writeTo(Writer w) throws IOException {
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(id);
+	}
+
+	@Override
+	public boolean equals(Object o) {
+		if(o instanceof OfferId)
+			return Arrays.equals(id, ((OfferId) o).id);
+		return false;
+	}
+}
diff --git a/api/net/sf/briar/api/protocol/Request.java b/api/net/sf/briar/api/protocol/Request.java
index ea3f461f3f01180739904fd438c13edf1feb4a0f..bc21df77ccb26b1b87e6ea68db54e042e67655cc 100644
--- a/api/net/sf/briar/api/protocol/Request.java
+++ b/api/net/sf/briar/api/protocol/Request.java
@@ -11,6 +11,12 @@ public interface Request {
 	 */
 	static final int MAX_SIZE = (1024 * 1024) - 100;
 
+	/**
+	 * Returns the unique identifier of the offer to which this request
+	 * responds.
+	 */
+	OfferId getOfferId();
+
 	/**
 	 * Returns a sequence of bits corresponding to the sequence of messages in
 	 * the offer, where the i^th bit is set if the i^th message should be sent.
diff --git a/api/net/sf/briar/api/protocol/Tags.java b/api/net/sf/briar/api/protocol/Tags.java
index 3b0071d4cae947cc6d5c4a486ac6f8f12cbd30eb..101b1e6577017ecb940230243c266cc99edc5feb 100644
--- a/api/net/sf/briar/api/protocol/Tags.java
+++ b/api/net/sf/briar/api/protocol/Tags.java
@@ -17,7 +17,8 @@ public interface Tags {
 	static final int MESSAGE = 7;
 	static final int MESSAGE_ID = 8;
 	static final int OFFER = 9;
-	static final int REQUEST = 10;
-	static final int SUBSCRIPTIONS = 11;
-	static final int TRANSPORTS = 12;
+	static final int OFFER_ID = 10;
+	static final int REQUEST = 11;
+	static final int SUBSCRIPTIONS = 12;
+	static final int TRANSPORTS = 13;
 }
diff --git a/api/net/sf/briar/api/protocol/writers/OfferWriter.java b/api/net/sf/briar/api/protocol/writers/OfferWriter.java
index 5ad5ddd2e5195c6f5a6d4dbfc335d3d045b5a94f..6f89b3b87fe9304608f4e8677c05a0847190334b 100644
--- a/api/net/sf/briar/api/protocol/writers/OfferWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/OfferWriter.java
@@ -3,6 +3,7 @@ package net.sf.briar.api.protocol.writers;
 import java.io.IOException;
 
 import net.sf.briar.api.protocol.MessageId;
+import net.sf.briar.api.protocol.OfferId;
 
 /** An interface for creating a have notification. */
 public interface OfferWriter {
@@ -13,6 +14,6 @@ public interface OfferWriter {
 	 */
 	boolean writeMessageId(MessageId m) throws IOException;
 
-	/** Finishes writing the offer. */
-	void finish() throws IOException;
+	/** Finishes writing the offer and returns its unique identifier. */
+	OfferId finish() throws IOException;
 }
diff --git a/api/net/sf/briar/api/protocol/writers/RequestWriter.java b/api/net/sf/briar/api/protocol/writers/RequestWriter.java
index 335753f6c2ddcf2f7b4625e3deb427c99a3dbc4d..d284f5c0c17e521fd67fe08b90bf6f5a9c8906d2 100644
--- a/api/net/sf/briar/api/protocol/writers/RequestWriter.java
+++ b/api/net/sf/briar/api/protocol/writers/RequestWriter.java
@@ -3,9 +3,11 @@ package net.sf.briar.api.protocol.writers;
 import java.io.IOException;
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
+
 /** An interface for creating a request packet. */
 public interface RequestWriter {
 
 	/** Writes the contents of the request. */
-	void writeBitmap(BitSet b, int length) throws IOException;
+	void writeRequest(OfferId offerId, BitSet b, int length) throws IOException;
 }
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index 2eb0dde3880619bcb066010efcd5221276545db7..eb8e93375e0425c26e2fa318685f3a8b09c736be 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -815,7 +815,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							db.abortTransaction(txn);
 							throw e;
 						}
-						r.writeBitmap(request, offered.size());
+						r.writeRequest(o.getId(), request, offered.size());
 					} finally {
 						subscriptionLock.readLock().unlock();
 					}
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index d99f65f4ed7fc4d616ad3eaf3a4cdcc2aaf5a7f9..466efead756b2c75674443ed1fe9316cf4cdef00 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -627,7 +627,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 							db.abortTransaction(txn);
 							throw e;
 						}
-						r.writeBitmap(request, offered.size());
+						r.writeRequest(o.getId(), request, offered.size());
 					}
 				}
 			}
diff --git a/components/net/sf/briar/protocol/AckReader.java b/components/net/sf/briar/protocol/AckReader.java
index 77ba91d23d1212333d4c5380c2792de84e241631..96c74d70db08fd6d1b5d5ece38ea575d7f47eb90 100644
--- a/components/net/sf/briar/protocol/AckReader.java
+++ b/components/net/sf/briar/protocol/AckReader.java
@@ -10,14 +10,11 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class AckReader implements ObjectReader<Ack> {
 
 	private final ObjectReader<BatchId> batchIdReader;
 	private final AckFactory ackFactory;
 
-	@Inject
 	AckReader(ObjectReader<BatchId> batchIdReader, AckFactory ackFactory) {
 		this.batchIdReader = batchIdReader;
 		this.ackFactory = ackFactory;
diff --git a/components/net/sf/briar/protocol/AuthorReader.java b/components/net/sf/briar/protocol/AuthorReader.java
index e0d73ed3ba2d0b91a3e32d99f991f950cbe193cf..4ce4cc8e405dd2bbd24516ce81f4fd5d83fc197b 100644
--- a/components/net/sf/briar/protocol/AuthorReader.java
+++ b/components/net/sf/briar/protocol/AuthorReader.java
@@ -11,14 +11,11 @@ import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class AuthorReader implements ObjectReader<Author> {
 
 	private final MessageDigest messageDigest;
 	private final AuthorFactory authorFactory;
 
-	@Inject
 	AuthorReader(CryptoComponent crypto, AuthorFactory authorFactory) {
 		messageDigest = crypto.getMessageDigest();
 		this.authorFactory = authorFactory;
diff --git a/components/net/sf/briar/protocol/BatchReader.java b/components/net/sf/briar/protocol/BatchReader.java
index 15ccb655cd310cd41be88130a5459f90727ca49d..bc41e694e8e7284ded8d25661a53c1e1cfa4b002 100644
--- a/components/net/sf/briar/protocol/BatchReader.java
+++ b/components/net/sf/briar/protocol/BatchReader.java
@@ -13,15 +13,12 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class BatchReader implements ObjectReader<Batch> {
 
 	private final MessageDigest messageDigest;
 	private final ObjectReader<Message> messageReader;
 	private final BatchFactory batchFactory;
 
-	@Inject
 	BatchReader(CryptoComponent crypto, ObjectReader<Message> messageReader,
 			BatchFactory batchFactory) {
 		messageDigest = crypto.getMessageDigest();
diff --git a/components/net/sf/briar/protocol/GroupReader.java b/components/net/sf/briar/protocol/GroupReader.java
index dd61419a0347e0c4d8884f3d73b5706379a6ab46..d855850dd437ee345ba6012e9e3711647b5d9eaf 100644
--- a/components/net/sf/briar/protocol/GroupReader.java
+++ b/components/net/sf/briar/protocol/GroupReader.java
@@ -11,14 +11,11 @@ import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class GroupReader implements ObjectReader<Group> {
 
 	private final MessageDigest messageDigest;
 	private final GroupFactory groupFactory;
 
-	@Inject
 	GroupReader(CryptoComponent crypto, GroupFactory groupFactory) {
 		messageDigest = crypto.getMessageDigest();
 		this.groupFactory = groupFactory;
diff --git a/components/net/sf/briar/protocol/MessageReader.java b/components/net/sf/briar/protocol/MessageReader.java
index 96b6824c5d2cc21473375411ece6b9d747e007c1..5319c11710b781d641950ad5592e142df07038f7 100644
--- a/components/net/sf/briar/protocol/MessageReader.java
+++ b/components/net/sf/briar/protocol/MessageReader.java
@@ -18,8 +18,6 @@ import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class MessageReader implements ObjectReader<Message> {
 
 	private final ObjectReader<MessageId> messageIdReader;
@@ -29,7 +27,6 @@ class MessageReader implements ObjectReader<Message> {
 	private final Signature signature;
 	private final MessageDigest messageDigest;
 
-	@Inject
 	MessageReader(CryptoComponent crypto,
 			ObjectReader<MessageId> messageIdReader,
 			ObjectReader<Group> groupReader,
diff --git a/components/net/sf/briar/protocol/OfferFactory.java b/components/net/sf/briar/protocol/OfferFactory.java
index 6f19d4e29192edd0eadd298b67d398f050391337..c9a1c33cc790be75f19dc6cf93a607dc18196762 100644
--- a/components/net/sf/briar/protocol/OfferFactory.java
+++ b/components/net/sf/briar/protocol/OfferFactory.java
@@ -4,8 +4,9 @@ import java.util.Collection;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 
 interface OfferFactory {
 
-	Offer createOffer(Collection<MessageId> offered);
+	Offer createOffer(OfferId id, Collection<MessageId> offered);
 }
diff --git a/components/net/sf/briar/protocol/OfferFactoryImpl.java b/components/net/sf/briar/protocol/OfferFactoryImpl.java
index 075527d14768faa86d022cfe308f8f7eda203b4b..f76802c2053216a7fb36830afd499dab595b5071 100644
--- a/components/net/sf/briar/protocol/OfferFactoryImpl.java
+++ b/components/net/sf/briar/protocol/OfferFactoryImpl.java
@@ -4,10 +4,11 @@ import java.util.Collection;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 
 class OfferFactoryImpl implements OfferFactory {
 
-	public Offer createOffer(Collection<MessageId> offered) {
-		return new OfferImpl(offered);
+	public Offer createOffer(OfferId id, Collection<MessageId> offered) {
+		return new OfferImpl(id, offered);
 	}
 }
diff --git a/components/net/sf/briar/protocol/OfferIdReader.java b/components/net/sf/briar/protocol/OfferIdReader.java
new file mode 100644
index 0000000000000000000000000000000000000000..52485760cfbb327948b0a6057ed824b15f6f1054
--- /dev/null
+++ b/components/net/sf/briar/protocol/OfferIdReader.java
@@ -0,0 +1,20 @@
+package net.sf.briar.protocol;
+
+import java.io.IOException;
+
+import net.sf.briar.api.protocol.OfferId;
+import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.UniqueId;
+import net.sf.briar.api.serial.FormatException;
+import net.sf.briar.api.serial.ObjectReader;
+import net.sf.briar.api.serial.Reader;
+
+class OfferIdReader implements ObjectReader<OfferId> {
+
+	public OfferId readObject(Reader r) throws IOException {
+		r.readUserDefinedTag(Tags.OFFER_ID);
+		byte[] b = r.readBytes(UniqueId.LENGTH);
+		if(b.length != UniqueId.LENGTH) throw new FormatException();
+		return new OfferId(b);
+	}
+}
diff --git a/components/net/sf/briar/protocol/OfferImpl.java b/components/net/sf/briar/protocol/OfferImpl.java
index de892202e64c2fc1fe435767993734920aeda5cf..0ce2e6bfe943e60c55e0e8e95739b1e82557c84b 100644
--- a/components/net/sf/briar/protocol/OfferImpl.java
+++ b/components/net/sf/briar/protocol/OfferImpl.java
@@ -4,15 +4,22 @@ import java.util.Collection;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 
 class OfferImpl implements Offer {
 
+	private final OfferId id;
 	private final Collection<MessageId> offered;
 
-	OfferImpl(Collection<MessageId> offered) {
+	OfferImpl(OfferId id, Collection<MessageId> offered) {
+		this.id = id;
 		this.offered = offered;
 	}
 
+	public OfferId getId() {
+		return id;
+	}
+
 	public Collection<MessageId> getMessageIds() {
 		return offered;
 	}
diff --git a/components/net/sf/briar/protocol/OfferReader.java b/components/net/sf/briar/protocol/OfferReader.java
index 459310dec1f056b9521266603794a390990c6b83..e269e865a6048bc768cc8a2a15c9277ab0add702 100644
--- a/components/net/sf/briar/protocol/OfferReader.java
+++ b/components/net/sf/briar/protocol/OfferReader.java
@@ -1,40 +1,47 @@
 package net.sf.briar.protocol;
 
 import java.io.IOException;
+import java.security.MessageDigest;
 import java.util.Collection;
 
+import net.sf.briar.api.crypto.CryptoComponent;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class OfferReader implements ObjectReader<Offer> {
 
+	private final MessageDigest messageDigest;
 	private final ObjectReader<MessageId> messageIdReader;
 	private final OfferFactory offerFactory;
 
-	@Inject
-	OfferReader(ObjectReader<MessageId> messageIdReader,
+	OfferReader(CryptoComponent crypto, ObjectReader<MessageId> messageIdReader,
 			OfferFactory offerFactory) {
+		messageDigest = crypto.getMessageDigest();
 		this.messageIdReader = messageIdReader;
 		this.offerFactory = offerFactory;
 	}
 
 	public Offer readObject(Reader r) throws IOException {
-		// Initialise the consumer
+		// Initialise the consumers
 		Consumer counting = new CountingConsumer(Offer.MAX_SIZE);
+		DigestingConsumer digesting = new DigestingConsumer(messageDigest);
+		messageDigest.reset();
 		// Read the data
 		r.addConsumer(counting);
+		r.addConsumer(digesting);
 		r.readUserDefinedTag(Tags.OFFER);
 		r.addObjectReader(Tags.MESSAGE_ID, messageIdReader);
 		Collection<MessageId> messages = r.readList(MessageId.class);
 		r.removeObjectReader(Tags.MESSAGE_ID);
+		r.removeConsumer(digesting);
 		r.removeConsumer(counting);
 		// Build and return the offer
-		return offerFactory.createOffer(messages);
+		OfferId id = new OfferId(messageDigest.digest());
+		return offerFactory.createOffer(id, messages);
 	}
 }
diff --git a/components/net/sf/briar/protocol/ProtocolModule.java b/components/net/sf/briar/protocol/ProtocolModule.java
index 6d36d8efb38cc18522aa7a1f9d5a7f83dcb95c53..79d4bac5867c3dae02210951d8c2b7194e535492 100644
--- a/components/net/sf/briar/protocol/ProtocolModule.java
+++ b/components/net/sf/briar/protocol/ProtocolModule.java
@@ -12,6 +12,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.ProtocolReaderFactory;
 import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.SubscriptionUpdate;
@@ -81,14 +82,21 @@ public class ProtocolModule extends AbstractModule {
 	}
 
 	@Provides
-	ObjectReader<Offer> getOfferReader(ObjectReader<MessageId> messageIdReader,
+	ObjectReader<OfferId> getOfferIdReader() {
+		return new OfferIdReader();
+	}
+
+	@Provides
+	ObjectReader<Offer> getOfferReader(CryptoComponent crypto,
+			ObjectReader<MessageId> messageIdReader,
 			OfferFactory offerFactory) {
-		return new OfferReader(messageIdReader, offerFactory);
+		return new OfferReader(crypto, messageIdReader, offerFactory);
 	}
 
 	@Provides
-	ObjectReader<Request> getRequestReader(RequestFactory requestFactory) {
-		return new RequestReader(requestFactory);
+	ObjectReader<Request> getRequestReader(ObjectReader<OfferId> offerIdReader,
+			RequestFactory requestFactory) {
+		return new RequestReader(offerIdReader, requestFactory);
 	}
 
 	@Provides
diff --git a/components/net/sf/briar/protocol/RequestFactory.java b/components/net/sf/briar/protocol/RequestFactory.java
index 005982b826a5c32ae72efceb1fe563d24d6406ec..09459765a69e7c3a9eaa7ee57944ef6e865c5df7 100644
--- a/components/net/sf/briar/protocol/RequestFactory.java
+++ b/components/net/sf/briar/protocol/RequestFactory.java
@@ -2,9 +2,10 @@ package net.sf.briar.protocol;
 
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 
 interface RequestFactory {
 
-	Request createRequest(BitSet requested);
+	Request createRequest(OfferId offerId, BitSet requested);
 }
diff --git a/components/net/sf/briar/protocol/RequestFactoryImpl.java b/components/net/sf/briar/protocol/RequestFactoryImpl.java
index 0c2c77cb1cd324c4f292adeba8da1125dfbd21f2..68cce91c54c76c5a595490d3a09f4337cfbd6bd4 100644
--- a/components/net/sf/briar/protocol/RequestFactoryImpl.java
+++ b/components/net/sf/briar/protocol/RequestFactoryImpl.java
@@ -2,11 +2,12 @@ package net.sf.briar.protocol;
 
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 
 class RequestFactoryImpl implements RequestFactory {
 
-	public Request createRequest(BitSet requested) {
-		return new RequestImpl(requested);
+	public Request createRequest(OfferId offerId, BitSet requested) {
+		return new RequestImpl(offerId, requested);
 	}
 }
diff --git a/components/net/sf/briar/protocol/RequestImpl.java b/components/net/sf/briar/protocol/RequestImpl.java
index ddb31898aeb2f4f02ae581ce889eaebf22894d09..42d3247ee94965b0eb7bac502198322831436575 100644
--- a/components/net/sf/briar/protocol/RequestImpl.java
+++ b/components/net/sf/briar/protocol/RequestImpl.java
@@ -2,16 +2,23 @@ package net.sf.briar.protocol;
 
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 
 class RequestImpl implements Request {
 
+	private final OfferId offerId;
 	private final BitSet requested;
 
-	RequestImpl(BitSet requested) {
+	RequestImpl(OfferId offerId, BitSet requested) {
+		this.offerId = offerId;
 		this.requested = requested;
 	}
 
+	public OfferId getOfferId() {
+		return offerId;
+	}
+
 	public BitSet getBitmap() {
 		return requested;
 	}
diff --git a/components/net/sf/briar/protocol/RequestReader.java b/components/net/sf/briar/protocol/RequestReader.java
index 567779007613ce62d383f52e009f43239574d3ab..901a557564f04c107510c9d875bbb368ac0e02a0 100644
--- a/components/net/sf/briar/protocol/RequestReader.java
+++ b/components/net/sf/briar/protocol/RequestReader.java
@@ -3,20 +3,21 @@ package net.sf.briar.protocol;
 import java.io.IOException;
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class RequestReader implements ObjectReader<Request> {
 
+	private final ObjectReader<OfferId> offerIdReader;
 	private final RequestFactory requestFactory;
 
-	@Inject
-	RequestReader(RequestFactory requestFactory) {
+	RequestReader(ObjectReader<OfferId> offerIdReader,
+			RequestFactory requestFactory) {
+		this.offerIdReader = offerIdReader;
 		this.requestFactory = requestFactory;
 	}
 
@@ -26,6 +27,8 @@ class RequestReader implements ObjectReader<Request> {
 		// Read the data
 		r.addConsumer(counting);
 		r.readUserDefinedTag(Tags.REQUEST);
+		r.addObjectReader(Tags.OFFER_ID, offerIdReader);
+		OfferId offerId = r.readUserDefined(Tags.OFFER_ID, OfferId.class);
 		byte[] bitmap = r.readBytes(Request.MAX_SIZE);
 		r.removeConsumer(counting);
 		// Convert the bitmap into a BitSet
@@ -36,6 +39,6 @@ class RequestReader implements ObjectReader<Request> {
 				if((bitmap[i] & bit) != 0) b.set(i * 8 + j);
 			}
 		}
-		return requestFactory.createRequest(b);
+		return requestFactory.createRequest(offerId, b);
 	}
 }
diff --git a/components/net/sf/briar/protocol/SubscriptionReader.java b/components/net/sf/briar/protocol/SubscriptionReader.java
index 6c31dad6fb663c1dab24ef81c9d347e8e16dc28d..8faaf647e1b9283f7f2f791575e26f51a0295c45 100644
--- a/components/net/sf/briar/protocol/SubscriptionReader.java
+++ b/components/net/sf/briar/protocol/SubscriptionReader.java
@@ -10,14 +10,11 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class SubscriptionReader implements ObjectReader<SubscriptionUpdate> {
 
 	private final ObjectReader<Group> groupReader;
 	private final SubscriptionFactory subscriptionFactory;
 
-	@Inject
 	SubscriptionReader(ObjectReader<Group> groupReader,
 			SubscriptionFactory subscriptionFactory) {
 		this.groupReader = groupReader;
diff --git a/components/net/sf/briar/protocol/TransportReader.java b/components/net/sf/briar/protocol/TransportReader.java
index 92a2a479050265a53a98928d7af46d58dc42f459..2ea5e61b3bc548e8615af0ad3ae9a23d186c9986 100644
--- a/components/net/sf/briar/protocol/TransportReader.java
+++ b/components/net/sf/briar/protocol/TransportReader.java
@@ -10,13 +10,10 @@ import net.sf.briar.api.serial.Consumer;
 import net.sf.briar.api.serial.ObjectReader;
 import net.sf.briar.api.serial.Reader;
 
-import com.google.inject.Inject;
-
 class TransportReader implements ObjectReader<TransportUpdate> {
 
 	private final TransportFactory transportFactory;
 
-	@Inject
 	TransportReader(TransportFactory transportFactory) {
 		this.transportFactory = transportFactory;
 	}
diff --git a/components/net/sf/briar/protocol/writers/OfferWriterImpl.java b/components/net/sf/briar/protocol/writers/OfferWriterImpl.java
index c0ec46fe94dbd01d5c6f43845fc5fbe35f5ee761..7bbac598dfa6f1135954a7d54aa4f7bb9a948160 100644
--- a/components/net/sf/briar/protocol/writers/OfferWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/OfferWriterImpl.java
@@ -2,9 +2,12 @@ package net.sf.briar.protocol.writers;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.security.DigestOutputStream;
+import java.security.MessageDigest;
 
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.writers.OfferWriter;
 import net.sf.briar.api.serial.Writer;
@@ -14,17 +17,20 @@ class OfferWriterImpl implements OfferWriter {
 
 	private final OutputStream out;
 	private final Writer w;
+	private final MessageDigest messageDigest;
 
-	private boolean started = false, finished = false;
+	private boolean started = false;
 
-	OfferWriterImpl(OutputStream out, WriterFactory writerFactory) {
-		this.out = out;
+	OfferWriterImpl(OutputStream out, WriterFactory writerFactory,
+			MessageDigest messageDigest) {
+		this.out = new DigestOutputStream(out, messageDigest);
 		w = writerFactory.createWriter(out);
+		this.messageDigest = messageDigest;
 	}
 
 	public boolean writeMessageId(MessageId m) throws IOException {
-		if(finished) throw new IllegalStateException();
 		if(!started) {
+			messageDigest.reset();
 			w.writeUserDefinedTag(Tags.OFFER);
 			w.writeListStart();
 			started = true;
@@ -35,15 +41,16 @@ class OfferWriterImpl implements OfferWriter {
 		return true;
 	}
 
-	public void finish() throws IOException {
-		if(finished) throw new IllegalStateException();
+	public OfferId finish() throws IOException {
 		if(!started) {
+			messageDigest.reset();
 			w.writeUserDefinedTag(Tags.OFFER);
 			w.writeListStart();
 			started = true;
 		}
 		w.writeListEnd();
 		out.flush();
-		finished = true;
+		started = false;
+		return new OfferId(messageDigest.digest());
 	}
 }
diff --git a/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java b/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java
index 25999403679a4406a1246f38cb61ebd77d4035c9..ce14908f1caee20b48565609407d37cf2af20950 100644
--- a/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java
+++ b/components/net/sf/briar/protocol/writers/ProtocolWriterFactoryImpl.java
@@ -36,7 +36,7 @@ class ProtocolWriterFactoryImpl implements ProtocolWriterFactory {
 	}
 
 	public OfferWriter createOfferWriter(OutputStream out) {
-		return new OfferWriterImpl(out, writerFactory);
+		return new OfferWriterImpl(out, writerFactory, messageDigest);
 	}
 
 	public RequestWriter createRequestWriter(OutputStream out) {
diff --git a/components/net/sf/briar/protocol/writers/RequestWriterImpl.java b/components/net/sf/briar/protocol/writers/RequestWriterImpl.java
index 28bce226a28ac08e43d0eac325d98deffe3a613d..d50909dabb849489df723087de4f26a77a5f2813 100644
--- a/components/net/sf/briar/protocol/writers/RequestWriterImpl.java
+++ b/components/net/sf/briar/protocol/writers/RequestWriterImpl.java
@@ -4,6 +4,7 @@ import java.io.IOException;
 import java.io.OutputStream;
 import java.util.BitSet;
 
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Tags;
 import net.sf.briar.api.protocol.writers.RequestWriter;
 import net.sf.briar.api.serial.Writer;
@@ -19,8 +20,11 @@ class RequestWriterImpl implements RequestWriter {
 		w = writerFactory.createWriter(out);
 	}
 
-	public void writeBitmap(BitSet b, int length) throws IOException {
+	public void writeRequest(OfferId offerId, BitSet b, int length)
+	throws IOException {
 		w.writeUserDefinedTag(Tags.REQUEST);
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(offerId.getBytes());
 		// If the number of bits isn't a multiple of 8, round up to a byte
 		int bytes = length % 8 == 0 ? length / 8 : length / 8 + 1;
 		byte[] bitmap = new byte[bytes];
diff --git a/test/net/sf/briar/FileReadWriteTest.java b/test/net/sf/briar/FileReadWriteTest.java
index 490c0d08b0c93680d030254b80c2186324725708..4988507957df41291d9977927d801662c90d2488 100644
--- a/test/net/sf/briar/FileReadWriteTest.java
+++ b/test/net/sf/briar/FileReadWriteTest.java
@@ -27,6 +27,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.ProtocolReader;
 import net.sf.briar.api.protocol.ProtocolReaderFactory;
 import net.sf.briar.api.protocol.Request;
@@ -78,6 +79,7 @@ public class FileReadWriteTest extends TestCase {
 	private final Message message, message1, message2, message3;
 	private final String authorName = "Alice";
 	private final String messageBody = "Hello world";
+	private final OfferId offerId;
 	private final Map<String, Map<String, String>> transports;
 
 	public FileReadWriteTest() throws Exception {
@@ -114,6 +116,7 @@ public class FileReadWriteTest extends TestCase {
 		message3 = messageEncoder.encodeMessage(MessageId.NONE, group1,
 				groupKeyPair.getPrivate(), author, authorKeyPair.getPrivate(),
 				messageBody.getBytes("UTF-8"));
+		offerId = new OfferId(TestUtils.getRandomId());
 		transports = Collections.singletonMap("foo",
 				Collections.singletonMap("bar", "baz"));
 	}
@@ -155,7 +158,7 @@ public class FileReadWriteTest extends TestCase {
 		BitSet requested = new BitSet(4);
 		requested.set(1);
 		requested.set(3);
-		r.writeBitmap(requested, 4);
+		r.writeRequest(offerId, requested, 4);
 		packetWriter.finishPacket();
 
 		SubscriptionWriter s =
@@ -231,6 +234,7 @@ public class FileReadWriteTest extends TestCase {
 		assertTrue(protocolReader.hasRequest());
 		Request r = protocolReader.readRequest();
 		packetReader.finishPacket();
+		assertEquals(offerId, r.getOfferId());
 		BitSet requested = r.getBitmap();
 		assertFalse(requested.get(0));
 		assertTrue(requested.get(1));
diff --git a/test/net/sf/briar/db/DatabaseCleanerImplTest.java b/test/net/sf/briar/db/DatabaseCleanerImplTest.java
index 691574da04a4f598616c3ae9a61ee5968fe55c66..1042338aea4e5afb3cac0dbbfcf5d934545f8b76 100644
--- a/test/net/sf/briar/db/DatabaseCleanerImplTest.java
+++ b/test/net/sf/briar/db/DatabaseCleanerImplTest.java
@@ -12,7 +12,7 @@ import org.junit.Test;
 public class DatabaseCleanerImplTest extends TestCase {
 
 	@Test
-	public void testStoppingCleanerWakesItUp() throws DbException {
+	public void testStoppingCleanerWakesItUp() throws Exception {
 		final CountDownLatch latch = new CountDownLatch(1);
 		Callback callback = new Callback() {
 
@@ -31,11 +31,7 @@ public class DatabaseCleanerImplTest extends TestCase {
 		long start = System.currentTimeMillis();
 		// Start the cleaner and check that shouldCheckFreeSpace() is called
 		cleaner.startCleaning();
-		try {
-			assertTrue(latch.await(5, TimeUnit.SECONDS));
-		} catch(InterruptedException e) {
-			fail();
-		}
+		assertTrue(latch.await(5, TimeUnit.SECONDS));
 		// Stop the cleaner (it should be waiting between sweeps)
 		cleaner.stopCleaning();
 		long end = System.currentTimeMillis();
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index 193f4392ea419bddd0daee58d2db8844f09e192e..f49ce59077b5458545c7e4cd47b9b228564fcbf4 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -12,10 +12,10 @@ import net.sf.briar.api.ContactId;
 import net.sf.briar.api.Rating;
 import net.sf.briar.api.db.DatabaseComponent;
 import net.sf.briar.api.db.DatabaseListener;
+import net.sf.briar.api.db.DatabaseListener.Event;
 import net.sf.briar.api.db.DbException;
 import net.sf.briar.api.db.NoSuchContactException;
 import net.sf.briar.api.db.Status;
-import net.sf.briar.api.db.DatabaseListener.Event;
 import net.sf.briar.api.protocol.Ack;
 import net.sf.briar.api.protocol.AuthorId;
 import net.sf.briar.api.protocol.Batch;
@@ -25,6 +25,7 @@ import net.sf.briar.api.protocol.GroupId;
 import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.SubscriptionUpdate;
 import net.sf.briar.api.protocol.TransportUpdate;
 import net.sf.briar.api.protocol.writers.AckWriter;
@@ -49,6 +50,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 	protected final ContactId contactId;
 	protected final GroupId groupId;
 	protected final MessageId messageId, parentId;
+	protected final OfferId offerId;
 	private final long timestamp;
 	private final int size;
 	private final byte[] raw;
@@ -65,6 +67,7 @@ public abstract class DatabaseComponentTest extends TestCase {
 		groupId = new GroupId(TestUtils.getRandomId());
 		messageId = new MessageId(TestUtils.getRandomId());
 		parentId = new MessageId(TestUtils.getRandomId());
+		offerId = new OfferId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
 		size = 1234;
 		raw = new byte[size];
@@ -1031,7 +1034,9 @@ public abstract class DatabaseComponentTest extends TestCase {
 			will(returnValue(true)); // Visible - do not request message # 1
 			oneOf(database).setStatusSeenIfVisible(txn, contactId, messageId2);
 			will(returnValue(false)); // Not visible - request message # 2
-			oneOf(requestWriter).writeBitmap(expectedRequest, 3);
+			oneOf(offer).getId();
+			will(returnValue(offerId));
+			oneOf(requestWriter).writeRequest(offerId, expectedRequest, 3);
 		}});
 		DatabaseComponent db = createDatabaseComponent(database, cleaner);
 
diff --git a/test/net/sf/briar/protocol/ProtocolReadWriteTest.java b/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
index cee401bc54703235fd196a718978a87731876cc3..3757ad9f04f19b04a7fd47e8ff24ca79031f2417 100644
--- a/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
+++ b/test/net/sf/briar/protocol/ProtocolReadWriteTest.java
@@ -17,6 +17,7 @@ import net.sf.briar.api.protocol.Message;
 import net.sf.briar.api.protocol.MessageEncoder;
 import net.sf.briar.api.protocol.MessageId;
 import net.sf.briar.api.protocol.Offer;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.ProtocolReader;
 import net.sf.briar.api.protocol.ProtocolReaderFactory;
 import net.sf.briar.api.protocol.Request;
@@ -46,6 +47,7 @@ public class ProtocolReadWriteTest extends TestCase {
 	private final Group group;
 	private final Message message;
 	private final String messageBody = "Hello world";
+	private final OfferId offerId;
 	private final BitSet bitSet;
 	private final Map<Group, Long> subscriptions;
 	private final Map<String, Map<String, String>> transports;
@@ -62,6 +64,7 @@ public class ProtocolReadWriteTest extends TestCase {
 		MessageEncoder messageEncoder = i.getInstance(MessageEncoder.class);
 		message = messageEncoder.encodeMessage(MessageId.NONE, group,
 				messageBody.getBytes("UTF-8"));
+		offerId = new OfferId(TestUtils.getRandomId());
 		bitSet = new BitSet();
 		bitSet.set(3);
 		bitSet.set(7);
@@ -90,7 +93,7 @@ public class ProtocolReadWriteTest extends TestCase {
 		o.finish();
 
 		RequestWriter r = writerFactory.createRequestWriter(out);
-		r.writeBitmap(bitSet, 10);
+		r.writeRequest(offerId, bitSet, 10);
 
 		SubscriptionWriter s = writerFactory.createSubscriptionWriter(out);
 		s.writeSubscriptions(subscriptions);
diff --git a/test/net/sf/briar/protocol/RequestReaderTest.java b/test/net/sf/briar/protocol/RequestReaderTest.java
index 4ff8865903419225eeb0af96adb74b3e176beb07..b9c2be3ef866801ac3907a4cf6bdcd4491a8ccf3 100644
--- a/test/net/sf/briar/protocol/RequestReaderTest.java
+++ b/test/net/sf/briar/protocol/RequestReaderTest.java
@@ -5,8 +5,10 @@ import java.io.ByteArrayOutputStream;
 import java.util.BitSet;
 
 import junit.framework.TestCase;
+import net.sf.briar.api.protocol.OfferId;
 import net.sf.briar.api.protocol.Request;
 import net.sf.briar.api.protocol.Tags;
+import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.serial.FormatException;
 import net.sf.briar.api.serial.Reader;
 import net.sf.briar.api.serial.ReaderFactory;
@@ -38,7 +40,8 @@ public class RequestReaderTest extends TestCase {
 	@Test
 	public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
 		RequestFactory requestFactory = context.mock(RequestFactory.class);
-		RequestReader requestReader = new RequestReader(requestFactory);
+		RequestReader requestReader =
+			new RequestReader(new OfferIdReader(), requestFactory);
 
 		byte[] b = createRequest(true);
 		ByteArrayInputStream in = new ByteArrayInputStream(b);
@@ -56,10 +59,12 @@ public class RequestReaderTest extends TestCase {
 	public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
 		final RequestFactory requestFactory =
 			context.mock(RequestFactory.class);
-		RequestReader requestReader = new RequestReader(requestFactory);
+		RequestReader requestReader =
+			new RequestReader(new OfferIdReader(), requestFactory);
 		final Request request = context.mock(Request.class);
 		context.checking(new Expectations() {{
-			oneOf(requestFactory).createRequest(with(any(BitSet.class)));
+			oneOf(requestFactory).createRequest(with(any(OfferId.class)),
+					with(any(BitSet.class)));
 			will(returnValue(request));
 		}});
 
@@ -95,8 +100,8 @@ public class RequestReaderTest extends TestCase {
 			// Deserialise the request
 			ByteArrayInputStream in = new ByteArrayInputStream(b);
 			Reader reader = readerFactory.createReader(in);
-			RequestReader requestReader =
-				new RequestReader(new RequestFactoryImpl());
+			RequestReader requestReader = new RequestReader(new OfferIdReader(),
+						new RequestFactoryImpl());
 			reader.addObjectReader(Tags.REQUEST, requestReader);
 			Request r = reader.readUserDefined(Tags.REQUEST, Request.class);
 			BitSet decoded = r.getBitmap();
@@ -115,10 +120,15 @@ public class RequestReaderTest extends TestCase {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		w.writeUserDefinedTag(Tags.REQUEST);
-		// Allow one byte for the REQUEST tag, one byte for the BYTES tag, and
-		// five bytes for the length as an int32
-		if(tooBig) w.writeBytes(new byte[Request.MAX_SIZE - 6]);
-		else w.writeBytes(new byte[Request.MAX_SIZE - 7]);
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(new byte[UniqueId.LENGTH]);
+		// Allow one byte for the REQUEST tag, one byte for the OFFER_ID tag,
+		// one byte for the BYTES tag, one byte for the length as a uint7,
+		// UniqueID.LENGTH bytes for the offer ID, one byte for the BYTES tag,
+		// and five bytes for the length as an int32
+		int overhead = UniqueId.LENGTH + 10;
+		if(tooBig) w.writeBytes(new byte[Request.MAX_SIZE - overhead + 1]);
+		else w.writeBytes(new byte[Request.MAX_SIZE - overhead]);
 		assertEquals(tooBig, out.size() > Request.MAX_SIZE);
 		return out.toByteArray();
 	}
@@ -127,6 +137,8 @@ public class RequestReaderTest extends TestCase {
 		ByteArrayOutputStream out = new ByteArrayOutputStream();
 		Writer w = writerFactory.createWriter(out);
 		w.writeUserDefinedTag(Tags.REQUEST);
+		w.writeUserDefinedTag(Tags.OFFER_ID);
+		w.writeBytes(new byte[UniqueId.LENGTH]);
 		w.writeBytes(bitmap);
 		return out.toByteArray();
 	}
diff --git a/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java b/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java
index b612f95bb14873d902898a81ebf568b7caea1226..e7e5d7e73f3577cc18fba3bad26003a6a3efa749 100644
--- a/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java
+++ b/test/net/sf/briar/protocol/writers/RequestWriterImplTest.java
@@ -5,6 +5,8 @@ import java.io.IOException;
 import java.util.BitSet;
 
 import junit.framework.TestCase;
+import net.sf.briar.api.protocol.OfferId;
+import net.sf.briar.api.protocol.UniqueId;
 import net.sf.briar.api.protocol.writers.RequestWriter;
 import net.sf.briar.api.serial.WriterFactory;
 import net.sf.briar.serial.SerialModule;
@@ -18,11 +20,13 @@ import com.google.inject.Injector;
 public class RequestWriterImplTest extends TestCase {
 
 	private final WriterFactory writerFactory;
+	private final OfferId offerId;
 
 	public RequestWriterImplTest() {
 		super();
 		Injector i = Guice.createInjector(new SerialModule());
 		writerFactory = i.getInstance(WriterFactory.class);
+		offerId = new OfferId(new byte[UniqueId.LENGTH]);
 	}
 
 	@Test
@@ -41,10 +45,14 @@ public class RequestWriterImplTest extends TestCase {
 		b.set(11);
 		b.set(12);
 		b.set(15);
-		r.writeBitmap(b, 16);
-		// Short user tag 10, short bytes with length 2, 0xD959
+		r.writeRequest(offerId, b, 16);
+		// Short user tag 11, short user tag 10, bytes with length 32 as a
+		// uint7, 32 zero bytes, short bytes with length 2, 0xD959
 		byte[] output = out.toByteArray();
-		assertEquals("CA" + "92" + "D959", StringUtils.toHexString(output));
+		assertEquals("CB" + "CA" + "F6" + "20"
+				+ "00000000000000000000000000000000"
+				+ "00000000000000000000000000000000"
+				+ "92" + "D959", StringUtils.toHexString(output));
 	}
 
 	@Test
@@ -62,9 +70,13 @@ public class RequestWriterImplTest extends TestCase {
 		b.set(9);
 		b.set(11);
 		b.set(12);
-		r.writeBitmap(b, 13);
-		// Short user tag 10, short bytes with length 2, 0x59D8
+		r.writeRequest(offerId, b, 13);
+		// Short user tag 11, short user tag 10, bytes with length 32 as a
+		// uint7, 32 zero bytes, short bytes with length 2, 0x59D8
 		byte[] output = out.toByteArray();
-		assertEquals("CA" + "92" + "59D8", StringUtils.toHexString(output));
+		assertEquals("CB" + "CA" + "F6" + "20"
+				+ "00000000000000000000000000000000"
+				+ "00000000000000000000000000000000"
+				+ "92" + "59D8", StringUtils.toHexString(output));
 	}
 }