diff --git a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java
index bf83fb6a28a8867e6aff30c1dc56f1d1b1ccd020..edc62948dd5681e84bd72f1834b6158f31833d30 100644
--- a/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java
+++ b/briar-core/src/main/java/org/briarproject/briar/client/BdfIncomingMessageHook.java
@@ -16,8 +16,6 @@ import org.briarproject.bramble.api.sync.ValidationManager.IncomingMessageHook;
 
 import javax.annotation.concurrent.Immutable;
 
-import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-
 @Immutable
 @NotNullByDefault
 public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
@@ -57,9 +55,7 @@ public abstract class BdfIncomingMessageHook implements IncomingMessageHook {
 	public boolean incomingMessage(Transaction txn, Message m, Metadata meta)
 			throws DbException, InvalidMessageException {
 		try {
-			byte[] raw = m.getRaw();
-			BdfList body = clientHelper.toList(raw, MESSAGE_HEADER_LENGTH,
-					raw.length - MESSAGE_HEADER_LENGTH);
+			BdfList body = clientHelper.toList(m);
 			BdfDictionary metaDictionary = metadataParser.parse(meta);
 			return incomingMessage(txn, m, body, metaDictionary);
 		} catch (FormatException e) {
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
index df0a719005c2b6fb632f73370b3e1f23f577b0d7..43f243a0155329feeb0150cf76936fb78e941f18 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeProtocolEngine.java
@@ -33,11 +33,13 @@ import org.briarproject.briar.api.introduction.event.IntroductionSucceededEvent;
 
 import java.security.GeneralSecurityException;
 import java.util.Map;
+import java.util.logging.Logger;
 
 import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static java.util.logging.Level.WARNING;
 import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
 import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_AUTH;
 import static org.briarproject.briar.introduction.IntroduceeState.AWAIT_RESPONSES;
@@ -51,6 +53,9 @@ import static org.briarproject.briar.introduction.IntroduceeState.START;
 class IntroduceeProtocolEngine
 		extends AbstractProtocolEngine<IntroduceeSession> {
 
+	private final static Logger LOG =
+			Logger.getLogger(IntroduceeProtocolEngine.class.getSimpleName());
+
 	private final IntroductionCrypto crypto;
 	private final KeyManager keyManager;
 	private final TransportPropertyManager transportPropertyManager;
@@ -383,11 +388,12 @@ class IntroduceeProtocolEngine
 			bobMacKey = crypto.deriveMacKey(masterKey, false);
 			SecretKey ourMacKey = s.getLocal().alice ? aliceMacKey : bobMacKey;
 			LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
-			mac = crypto.authMac(ourMacKey, s, localAuthor.getId(),
-					s.getLocal().alice);
+			mac = crypto.authMac(ourMacKey, s, localAuthor.getId());
 			signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey());
 		} catch (GeneralSecurityException e) {
 			// TODO
+			if (LOG.isLoggable(WARNING))
+				LOG.log(WARNING, e.toString(), e);
 			return abort(txn, s);
 		}
 		if (s.getState() != AWAIT_AUTH) throw new AssertionError();
@@ -406,7 +412,7 @@ class IntroduceeProtocolEngine
 		LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
 		try {
 			crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId());
-			crypto.verifySignature(m.getSignature(), s, localAuthor.getId());
+			crypto.verifySignature(m.getSignature(), s);
 		} catch (GeneralSecurityException e) {
 			return abort(txn, s);
 		}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
index 8aeb28277a8be9ec59a18cc736bbb9160b8eaf55..b923baac83f0538a5dbe613ae140b77947f2fc7b 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroducerProtocolEngine.java
@@ -207,8 +207,10 @@ class IntroducerProtocolEngine
 			IntroducerSession s,
 			@Nullable String message, long timestamp) throws DbException {
 		// Send REQUEST messages
-		long localTimestamp =
-				Math.max(timestamp, getLocalTimestamp(s, s.getIntroduceeA()));
+		long maxIntroduceeTimestamp =
+				Math.max(getLocalTimestamp(s, s.getIntroduceeA()),
+						getLocalTimestamp(s, s.getIntroduceeB()));
+		long localTimestamp = Math.max(timestamp, maxIntroduceeTimestamp);
 		Message sentA = sendRequestMessage(txn, s.getIntroduceeA(),
 				localTimestamp, s.getIntroduceeB().author, message
 		);
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java
index 7504270d91d871f26c6a53530e6c5d56044ed390..cc5c9ab1a239847b7365cb12b39db79207e98461 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCrypto.java
@@ -49,7 +49,7 @@ interface IntroductionCrypto {
 	 * transport properties, Author IDs and timestamps of the accept message.
 	 */
 	byte[] authMac(SecretKey macKey, IntroduceeSession s,
-			AuthorId localAuthorId, boolean alice);
+			AuthorId localAuthorId);
 
 	/**
 	 * Verifies a received MAC
@@ -74,12 +74,12 @@ interface IntroductionCrypto {
 			throws GeneralSecurityException;
 
 	/**
-	 * Verifies the signature on a corresponding MAC key.
+	 * Verifies the signature on a nonce derived from the MAC key.
 	 *
 	 * @throws GeneralSecurityException if the signature is invalid
 	 */
-	void verifySignature(byte[] signature, IntroduceeSession s,
-			AuthorId localAuthorId) throws GeneralSecurityException;
+	void verifySignature(byte[] signature, IntroduceeSession s)
+			throws GeneralSecurityException;
 
 	/**
 	 * Generates a MAC using the local MAC key.
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java
index 6712a3153aae4b214aca863180647b5ca1e82833..db24fda6b2efaafcd5903a4a4088573d5b59c547 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionCryptoImpl.java
@@ -13,12 +13,11 @@ import org.briarproject.bramble.api.data.BdfList;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorId;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
-import org.briarproject.bramble.api.plugin.TransportId;
-import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.briar.api.client.SessionId;
+import org.briarproject.briar.introduction.IntroduceeSession.Common;
+import org.briarproject.briar.introduction.IntroduceeSession.Remote;
 
 import java.security.GeneralSecurityException;
-import java.util.Map;
 
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
@@ -32,6 +31,7 @@ import static org.briarproject.briar.api.introduction.IntroductionConstants.LABE
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_MASTER_KEY;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
 import static org.briarproject.briar.api.introduction.IntroductionManager.CLIENT_VERSION;
+import static org.briarproject.briar.introduction.IntroduceeSession.Local;
 
 @Immutable
 @NotNullByDefault
@@ -114,27 +114,16 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 	@Override
 	@SuppressWarnings("ConstantConditions")
 	public byte[] authMac(SecretKey macKey, IntroduceeSession s,
-			AuthorId localAuthorId, boolean alice) {
+			AuthorId localAuthorId) {
+		// the macKey is not yet available in the session at this point
 		return authMac(macKey, s.getIntroducer().getId(), localAuthorId,
-				s.getRemote().author.getId(), s.getLocal().acceptTimestamp,
-				s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey,
-				s.getRemote().ephemeralPublicKey,
-				s.getLocal().transportProperties,
-				s.getRemote().transportProperties, alice);
+				s.getLocal(), s.getRemote());
 	}
 
 	byte[] authMac(SecretKey macKey, AuthorId introducerId,
-			AuthorId localAuthorId, AuthorId remoteAuthorId,
-			long acceptTimestamp, long remoteAcceptTimestamp,
-			byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey,
-			Map<TransportId, TransportProperties> transportProperties,
-			Map<TransportId, TransportProperties> remoteTransportProperties,
-			boolean alice) {
-		byte[] inputs =
-				getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId,
-						acceptTimestamp, remoteAcceptTimestamp,
-						ephemeralPublicKey, remoteEphemeralPublicKey,
-						transportProperties, remoteTransportProperties, alice);
+			AuthorId localAuthorId, Local local, Remote remote) {
+		byte[] inputs = getAuthMacInputs(introducerId, localAuthorId, local,
+				remote.author.getId(), remote);
 		return crypto.mac(
 				LABEL_AUTH_MAC,
 				macKey,
@@ -145,59 +134,43 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 	@Override
 	@SuppressWarnings("ConstantConditions")
 	public void verifyAuthMac(byte[] mac, IntroduceeSession s,
-			AuthorId localAuthorId)
-			throws GeneralSecurityException {
-		boolean alice = isAlice(localAuthorId, s.getRemote().author.getId());
+			AuthorId localAuthorId) throws GeneralSecurityException {
 		verifyAuthMac(mac, new SecretKey(s.getRemote().macKey),
-				s.getIntroducer().getId(), localAuthorId,
-				s.getRemote().author.getId(), s.getLocal().acceptTimestamp,
-				s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey,
-				s.getRemote().ephemeralPublicKey,
-				s.getLocal().transportProperties,
-				s.getRemote().transportProperties, !alice);
+				s.getIntroducer().getId(), localAuthorId, s.getLocal(),
+				s.getRemote().author.getId(), s.getRemote());
 	}
 
-	void verifyAuthMac(byte[] mac, SecretKey macKey,
-			AuthorId introducerId, AuthorId localAuthorId,
-			AuthorId remoteAuthorId, long acceptTimestamp,
-			long remoteAcceptTimestamp, byte[] ephemeralPublicKey,
-			byte[] remoteEphemeralPublicKey,
-			Map<TransportId, TransportProperties> transportProperties,
-			Map<TransportId, TransportProperties> remoteTransportProperties,
-			boolean alice) throws GeneralSecurityException {
-		byte[] inputs =
-				getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId,
-						acceptTimestamp, remoteAcceptTimestamp,
-						ephemeralPublicKey, remoteEphemeralPublicKey,
-						transportProperties, remoteTransportProperties, !alice);
+	void verifyAuthMac(byte[] mac, SecretKey macKey, AuthorId introducerId,
+			AuthorId localAuthorId, Common local, AuthorId remoteAuthorId,
+			Common remote) throws GeneralSecurityException {
+		// switch input for verification
+		byte[] inputs = getAuthMacInputs(introducerId, remoteAuthorId, remote,
+				localAuthorId, local);
 		if (!crypto.verifyMac(mac, LABEL_AUTH_MAC, macKey, inputs)) {
 			throw new GeneralSecurityException();
 		}
 	}
 
+	@SuppressWarnings("ConstantConditions")
 	private byte[] getAuthMacInputs(AuthorId introducerId,
-			AuthorId localAuthorId, AuthorId remoteAuthorId,
-			long acceptTimestamp, long remoteAcceptTimestamp,
-			byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey,
-			Map<TransportId, TransportProperties> transportProperties,
-			Map<TransportId, TransportProperties> remoteTransportProperties,
-			boolean alice) {
+			AuthorId localAuthorId, Common local, AuthorId remoteAuthorId,
+			Common remote) {
 		BdfList localInfo = BdfList.of(
 				localAuthorId,
-				acceptTimestamp,
-				ephemeralPublicKey,
-				clientHelper.toDictionary(transportProperties)
+				local.acceptTimestamp,
+				local.ephemeralPublicKey,
+				clientHelper.toDictionary(local.transportProperties)
 		);
 		BdfList remoteInfo = BdfList.of(
 				remoteAuthorId,
-				remoteAcceptTimestamp,
-				remoteEphemeralPublicKey,
-				clientHelper.toDictionary(remoteTransportProperties)
+				remote.acceptTimestamp,
+				remote.ephemeralPublicKey,
+				clientHelper.toDictionary(remote.transportProperties)
 		);
 		BdfList macList = BdfList.of(
 				introducerId,
-				alice ? localInfo : remoteInfo,
-				alice ? remoteInfo : localInfo
+				localInfo,
+				remoteInfo
 		);
 		try {
 			return clientHelper.toByteArray(macList);
@@ -218,8 +191,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public void verifySignature(byte[] signature, IntroduceeSession s,
-			AuthorId localAuthorId) throws GeneralSecurityException {
+	public void verifySignature(byte[] signature, IntroduceeSession s)
+			throws GeneralSecurityException {
 		SecretKey macKey = new SecretKey(s.getRemote().macKey);
 		verifySignature(macKey, s.getRemote().author.getPublicKey(), signature);
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java
index 4082b8dcc6cf30920cecb8a6f53da0f88273045c..89d26c73fa7f907e99589b2e201fbf34c308c6dd 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionManagerImpl.java
@@ -71,6 +71,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
 	private final IntroductionCrypto crypto;
 	private final IdentityManager identityManager;
 
+	private final Group localGroup;
+
 	@Inject
 	IntroductionManagerImpl(
 			DatabaseComponent db,
@@ -96,12 +98,13 @@ class IntroductionManagerImpl extends ConversationClientImpl
 		this.introduceeEngine = introduceeEngine;
 		this.crypto = crypto;
 		this.identityManager = identityManager;
+		this.localGroup =
+				contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
 	}
 
 	@Override
 	public void createLocalState(Transaction txn) throws DbException {
 		// Create a local group to store protocol sessions
-		Group localGroup = getLocalGroup();
 		if (db.containsGroup(txn, localGroup.getId())) return;
 		db.addGroup(txn, localGroup);
 		// Set up groups for communication with any pre-existing contacts
@@ -229,8 +232,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 		if (sessionId == null) return null;
 		BdfDictionary query = sessionParser.getSessionQuery(sessionId);
 		Map<MessageId, BdfDictionary> results = clientHelper
-				.getMessageMetadataAsDictionary(txn, getLocalGroup().getId(),
-						query);
+				.getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
 		if (results.size() > 1) throw new DbException();
 		if (results.isEmpty()) return null;
 		return new StoredSession(results.keySet().iterator().next(),
@@ -246,7 +248,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 
 	private MessageId createStorageId(Transaction txn) throws DbException {
 		Message m = clientHelper
-				.createMessageForStoringMetadata(getLocalGroup().getId());
+				.createMessageForStoringMetadata(localGroup.getId());
 		db.addLocalMessage(txn, m, new Metadata(), false);
 		return m.getId();
 	}
@@ -274,22 +276,28 @@ class IntroductionManagerImpl extends ConversationClientImpl
 	public boolean canIntroduce(Contact c1, Contact c2) throws DbException {
 		Transaction txn = db.startTransaction(true);
 		try {
-			// Look up the session, if there is one
-			Author introducer = identityManager.getLocalAuthor(txn);
-			SessionId sessionId =
-					crypto.getSessionId(introducer, c1.getAuthor(),
-							c2.getAuthor());
-			StoredSession ss = getSession(txn, sessionId);
-			if (ss == null) return true;
-			IntroducerSession session =
-					sessionParser.parseIntroducerSession(ss.bdfSession);
-			if (session.getState() == START) return true;
+			boolean can = canIntroduce(txn, c1, c2);
+			db.commitTransaction(txn);
+			return can;
 		} catch (FormatException e) {
 			throw new DbException(e);
 		} finally {
 			db.endTransaction(txn);
 		}
-		return false;
+	}
+
+	private boolean canIntroduce(Transaction txn, Contact c1, Contact c2)
+			throws DbException, FormatException {
+		// Look up the session, if there is one
+		Author introducer = identityManager.getLocalAuthor(txn);
+		SessionId sessionId =
+				crypto.getSessionId(introducer, c1.getAuthor(),
+						c2.getAuthor());
+		StoredSession ss = getSession(txn, sessionId);
+		if (ss == null) return true;
+		IntroducerSession session =
+				sessionParser.parseIntroducerSession(ss.bdfSession);
+		return session.getState() == START;
 	}
 
 	@Override
@@ -395,12 +403,12 @@ class IntroductionManagerImpl extends ConversationClientImpl
 									meta, status, ss.bdfSession));
 				} else if (type == ACCEPT) {
 					messages.add(
-							parseInvitationResponse(txn, contactGroupId, m,
-									meta, status, ss.bdfSession, true));
+							parseInvitationResponse(contactGroupId, m, meta,
+									status, ss.bdfSession, true));
 				} else if (type == DECLINE) {
 					messages.add(
-							parseInvitationResponse(txn, contactGroupId, m,
-									meta, status, ss.bdfSession, false));
+							parseInvitationResponse(contactGroupId, m, meta,
+									status, ss.bdfSession, false));
 				}
 			}
 			db.commitTransaction(txn);
@@ -435,8 +443,8 @@ class IntroductionManagerImpl extends ConversationClientImpl
 			author = session.getRemote().author;
 		} else throw new AssertionError();
 		Message msg = clientHelper.getMessage(txn, m);
-		BdfList body = clientHelper.getMessageAsList(txn, m);
-		if (msg == null || body == null) throw new AssertionError();
+		if (msg == null) throw new AssertionError();
+		BdfList body = clientHelper.toList(msg);
 		RequestMessage rm = messageParser.parseRequestMessage(msg, body);
 		String message = rm.getMessage();
 		LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
@@ -451,10 +459,9 @@ class IntroductionManagerImpl extends ConversationClientImpl
 				contactExists);
 	}
 
-	private IntroductionResponse parseInvitationResponse(Transaction txn,
-			GroupId contactGroupId, MessageId m, MessageMetadata meta,
-			MessageStatus status, BdfDictionary bdfSession, boolean accept)
-			throws FormatException, DbException {
+	private IntroductionResponse parseInvitationResponse(GroupId contactGroupId,
+			MessageId m, MessageMetadata meta, MessageStatus status,
+			BdfDictionary bdfSession, boolean accept) throws FormatException {
 		Role role = sessionParser.getRole(bdfSession);
 		SessionId sessionId;
 		Author author;
@@ -462,8 +469,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 			IntroducerSession session =
 					sessionParser.parseIntroducerSession(bdfSession);
 			sessionId = session.getSessionId();
-			LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
-			if (localAuthor.equals(session.getIntroduceeA().author)) {
+			if (contactGroupId.equals(session.getIntroduceeA().groupId)) {
 				author = session.getIntroduceeB().author;
 			} else {
 				author = session.getIntroduceeA().author;
@@ -485,13 +491,13 @@ class IntroductionManagerImpl extends ConversationClientImpl
 				.getIntroduceeSessionsByIntroducerQuery(introducer.getAuthor());
 		Map<MessageId, BdfDictionary> sessions;
 		try {
-			sessions = clientHelper.getMessageMetadataAsDictionary(txn,
-					getLocalGroup().getId(), query);
+			sessions = clientHelper
+					.getMessageMetadataAsDictionary(txn, localGroup.getId(),
+							query);
 		} catch (FormatException e) {
-			throw new AssertionError(e);
+			throw new DbException(e);
 		}
 		for (MessageId id : sessions.keySet()) {
-			db.deleteMessageMetadata(txn, id); // TODO needed?
 			db.removeMessage(txn, id);
 		}
 	}
@@ -501,10 +507,11 @@ class IntroductionManagerImpl extends ConversationClientImpl
 		BdfDictionary query = sessionEncoder.getIntroducerSessionsQuery();
 		Map<MessageId, BdfDictionary> sessions;
 		try {
-			sessions = clientHelper.getMessageMetadataAsDictionary(txn,
-					getLocalGroup().getId(), query);
+			sessions = clientHelper
+					.getMessageMetadataAsDictionary(txn, localGroup.getId(),
+							query);
 		} catch (FormatException e) {
-			throw new AssertionError();
+			throw new DbException();
 		}
 		LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
 		for (Entry<MessageId, BdfDictionary> session : sessions.entrySet()) {
@@ -512,7 +519,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 			try {
 				s = sessionParser.parseIntroducerSession(session.getValue());
 			} catch (FormatException e) {
-				throw new AssertionError();
+				throw new DbException();
 			}
 			if (s.getIntroduceeA().author.equals(c.getAuthor())) {
 				abortOrRemoveSessionWithIntroducee(txn, s, session.getKey(),
@@ -531,15 +538,10 @@ class IntroductionManagerImpl extends ConversationClientImpl
 			IntroducerSession session = introducerEngine.onAbortAction(txn, s);
 			storeSession(txn, storageId, session);
 		} else {
-			db.deleteMessageMetadata(txn, storageId); // TODO needed?
 			db.removeMessage(txn, storageId);
 		}
 	}
 
-	private Group getLocalGroup() {
-		return contactGroupFactory.createLocalGroup(CLIENT_ID, CLIENT_VERSION);
-	}
-
 	private static class StoredSession {
 
 		private final MessageId storageId;
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java
index 6b59a64ae4fb954b485e126e846d2fc887c6018c..362d7e247a7a199d3c94ca0e5651f27ed8a31a9e 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionValidator.java
@@ -22,7 +22,6 @@ import javax.annotation.concurrent.Immutable;
 import static org.briarproject.bramble.api.crypto.CryptoConstants.MAC_BYTES;
 import static org.briarproject.bramble.api.crypto.CryptoConstants.MAX_SIGNATURE_BYTES;
 import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
-import static org.briarproject.bramble.api.plugin.TransportId.MAX_TRANSPORT_ID_LENGTH;
 import static org.briarproject.bramble.util.ValidationUtils.checkLength;
 import static org.briarproject.bramble.util.ValidationUtils.checkSize;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
@@ -79,8 +78,8 @@ class IntroductionValidator extends BdfMessageValidator {
 		String msg = body.getOptionalString(3);
 		checkLength(msg, 1, MAX_REQUEST_MESSAGE_LENGTH);
 
-		BdfDictionary meta = messageEncoder
-				.encodeRequestMetadata(m.getTimestamp(), false, false, false);
+		BdfDictionary meta =
+				messageEncoder.encodeRequestMetadata(m.getTimestamp());
 		if (previousMessageId == null) {
 			return new BdfMessageContext(meta);
 		} else {
@@ -103,15 +102,13 @@ class IntroductionValidator extends BdfMessageValidator {
 		byte[] ephemeralPublicKey = body.getRaw(3);
 		checkLength(ephemeralPublicKey, 0, MAX_PUBLIC_KEY_LENGTH);
 
-		body.getLong(4);
+		long timestamp = body.getLong(4);
+		if (timestamp < 0) throw new FormatException();
 
 		BdfDictionary transportProperties = body.getDictionary(5);
 		if (transportProperties.size() < 1) throw new FormatException();
-		for (String tId : transportProperties.keySet()) {
-			checkLength(tId, 1, MAX_TRANSPORT_ID_LENGTH);
-			BdfDictionary tProps = transportProperties.getDictionary(tId);
-			clientHelper.parseAndValidateTransportProperties(tProps);
-		}
+		clientHelper
+				.parseAndValidateTransportPropertiesMap(transportProperties);
 
 		SessionId sessionId = new SessionId(sessionIdBytes);
 		BdfDictionary meta = messageEncoder
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java
index 2eed3057a0678723cded22c787391569a51e3ac8..1327b54a938150a5c14019ae5db11a9dab162934 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoder.java
@@ -17,8 +17,7 @@ import javax.annotation.Nullable;
 @NotNullByDefault
 interface MessageEncoder {
 
-	BdfDictionary encodeRequestMetadata(long timestamp, boolean local,
-			boolean read, boolean available);
+	BdfDictionary encodeRequestMetadata(long timestamp);
 
 	BdfDictionary encodeMetadata(MessageType type,
 			@Nullable SessionId sessionId, long timestamp, boolean local,
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java
index 89d3e40e5912324bdc33a13debc54183336f41ed..fb3d66f38012129401d21453d63b9ab431be13d1 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageEncoderImpl.java
@@ -47,11 +47,10 @@ class MessageEncoderImpl implements MessageEncoder {
 	}
 
 	@Override
-	public BdfDictionary encodeRequestMetadata(long timestamp,
-			boolean local, boolean read, boolean available) {
+	public BdfDictionary encodeRequestMetadata(long timestamp) {
 		BdfDictionary meta =
-				encodeMetadata(REQUEST, null, timestamp, local, read, false);
-		meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, available);
+				encodeMetadata(REQUEST, null, timestamp, false, false, false);
+		meta.put(MSG_KEY_AVAILABLE_TO_ANSWER, false);
 		return meta;
 	}
 
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoIntegrationTest.java
similarity index 56%
rename from briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java
rename to briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoIntegrationTest.java
index 59cf3b4f4ae9760312e0b7771753e9e3f8b445e0..81c2ae01c3c6a80389b5bc793615006f4a6df1a2 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoIntegrationTest.java
@@ -11,23 +11,25 @@ import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.bramble.api.properties.TransportProperties;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.test.BriarIntegrationTestComponent;
-import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
 import org.junit.Test;
 
 import java.util.Map;
 
 import javax.inject.Inject;
 
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getSecretKey;
 import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
-import static org.briarproject.bramble.util.StringUtils.fromHexString;
+import static org.briarproject.briar.introduction.IntroduceeSession.Local;
+import static org.briarproject.briar.introduction.IntroduceeSession.Remote;
+import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor;
+import static org.briarproject.briar.test.BriarTestUtils.getRealLocalAuthor;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertTrue;
 
-public class IntroductionCryptoImplTest extends BrambleTestCase {
+public class IntroductionCryptoIntegrationTest extends BrambleTestCase {
 
 	@Inject
 	ClientHelper clientHelper;
@@ -42,33 +44,28 @@ public class IntroductionCryptoImplTest extends BrambleTestCase {
 	private final LocalAuthor alice, bob;
 	private final long aliceAcceptTimestamp = 42L;
 	private final long bobAcceptTimestamp = 1337L;
-	private final SecretKey masterKey =
-			new SecretKey(getRandomBytes(SecretKey.LENGTH));
+	private final SecretKey masterKey = getSecretKey();
 	private final KeyPair aliceEphemeral, bobEphemeral;
 	private final Map<TransportId, TransportProperties> aliceTransport =
 			getTransportPropertiesMap(3);
 	private final Map<TransportId, TransportProperties> bobTransport =
 			getTransportPropertiesMap(3);
 
-	public IntroductionCryptoImplTest() {
-		BriarIntegrationTestComponent component =
-				DaggerBriarIntegrationTestComponent.builder().build();
+	public IntroductionCryptoIntegrationTest() {
+		IntroductionIntegrationTestComponent component =
+				DaggerIntroductionIntegrationTestComponent.builder().build();
 		component.inject(this);
 		crypto = new IntroductionCryptoImpl(cryptoComponent, clientHelper);
 
-		// create actual deterministic authors for testing
-		introducer = authorFactory
-				.createAuthor("Introducer", new byte[] {0x1, 0x2, 0x3});
-		alice = authorFactory.createLocalAuthor("Alice",
-				fromHexString(
-						"A626F080C94771698F86B4B4094C4F560904B53398805AE02BA2343F1829187A"),
-				fromHexString(
-						"60F010187AF91ACA15141E8C811EC8E79C7CAA6461C21A852BB03066C89B0A70"));
-		bob = authorFactory.createLocalAuthor("Bob",
-				fromHexString(
-						"A0D0FED1CE4674D8B6441AD0A664E41BF60D489F35DA11F52AF923540848546F"),
-				fromHexString(
-						"20B25BE7E999F68FE07189449E91984FA79121DBFF28A651669A3CF512D6A758"));
+		introducer = getRealAuthor(authorFactory);
+		LocalAuthor introducee1 =
+				getRealLocalAuthor(cryptoComponent, authorFactory);
+		LocalAuthor introducee2 =
+				getRealLocalAuthor(cryptoComponent, authorFactory);
+		boolean isAlice =
+				crypto.isAlice(introducee1.getId(), introducee2.getId());
+		alice = isAlice ? introducee1 : introducee2;
+		bob = isAlice ? introducee2 : introducee1;
 		aliceEphemeral = crypto.generateKeyPair();
 		bobEphemeral = crypto.generateKeyPair();
 	}
@@ -78,6 +75,9 @@ public class IntroductionCryptoImplTest extends BrambleTestCase {
 		SessionId s1 = crypto.getSessionId(introducer, alice, bob);
 		SessionId s2 = crypto.getSessionId(introducer, bob, alice);
 		assertEquals(s1, s2);
+
+		SessionId s3 = crypto.getSessionId(alice, bob, introducer);
+		assertNotEquals(s1, s3);
 	}
 
 	@Test
@@ -88,62 +88,66 @@ public class IntroductionCryptoImplTest extends BrambleTestCase {
 
 	@Test
 	public void testDeriveMasterKey() throws Exception {
-		SecretKey aliceMasterKey = crypto.deriveMasterKey(alice.getPublicKey(),
-				alice.getPrivateKey(), bob.getPublicKey(), true);
-		SecretKey bobMasterKey = crypto.deriveMasterKey(bob.getPublicKey(),
-				bob.getPrivateKey(), alice.getPublicKey(), false);
+		SecretKey aliceMasterKey =
+				crypto.deriveMasterKey(aliceEphemeral.getPublic().getEncoded(),
+						aliceEphemeral.getPrivate().getEncoded(),
+						bobEphemeral.getPublic().getEncoded(), true);
+		SecretKey bobMasterKey =
+				crypto.deriveMasterKey(bobEphemeral.getPublic().getEncoded(),
+						bobEphemeral.getPrivate().getEncoded(),
+						aliceEphemeral.getPublic().getEncoded(), false);
 		assertArrayEquals(aliceMasterKey.getBytes(), bobMasterKey.getBytes());
 	}
 
 	@Test
 	public void testAliceAuthMac() throws Exception {
 		SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true);
+		Local local = new Local(true, null, -1,
+				aliceEphemeral.getPublic().getEncoded(),
+				aliceEphemeral.getPrivate().getEncoded(), aliceTransport,
+				aliceAcceptTimestamp, aliceMacKey.getBytes());
+		Remote remote = new Remote(false, bob, null,
+				bobEphemeral.getPublic().getEncoded(), bobTransport,
+				bobAcceptTimestamp, null);
 		byte[] aliceMac =
 				crypto.authMac(aliceMacKey, introducer.getId(), alice.getId(),
-						bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp,
-						aliceEphemeral.getPublic().getEncoded(),
-						bobEphemeral.getPublic().getEncoded(), aliceTransport,
-						bobTransport, true);
+						local, remote);
 
+		// verify from Bob's perspective
 		crypto.verifyAuthMac(aliceMac, aliceMacKey, introducer.getId(),
-				bob.getId(), alice.getId(), bobAcceptTimestamp,
-				aliceAcceptTimestamp, bobEphemeral.getPublic().getEncoded(),
-				aliceEphemeral.getPublic().getEncoded(), bobTransport,
-				aliceTransport, true);
+				bob.getId(), remote, alice.getId(), local);
 	}
 
 	@Test
 	public void testBobAuthMac() throws Exception {
 		SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false);
+		Local local = new Local(false, null, -1,
+				bobEphemeral.getPublic().getEncoded(),
+				bobEphemeral.getPrivate().getEncoded(), bobTransport,
+				bobAcceptTimestamp, bobMacKey.getBytes());
+		Remote remote = new Remote(true, alice, null,
+				aliceEphemeral.getPublic().getEncoded(), aliceTransport,
+				aliceAcceptTimestamp, null);
 		byte[] bobMac =
 				crypto.authMac(bobMacKey, introducer.getId(), bob.getId(),
-						alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp,
-						bobEphemeral.getPublic().getEncoded(),
-						aliceEphemeral.getPublic().getEncoded(), bobTransport,
-						aliceTransport, false);
+						local, remote);
 
+		// verify from Alice's perspective
 		crypto.verifyAuthMac(bobMac, bobMacKey, introducer.getId(),
-				alice.getId(), bob.getId(), aliceAcceptTimestamp,
-				bobAcceptTimestamp, aliceEphemeral.getPublic().getEncoded(),
-				bobEphemeral.getPublic().getEncoded(), aliceTransport,
-				bobTransport, false);
+				alice.getId(), remote, bob.getId(), local);
 	}
 
 	@Test
 	public void testSign() throws Exception {
-		KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
 		SecretKey macKey = crypto.deriveMacKey(masterKey, true);
-		byte[] signature =
-				crypto.sign(macKey, keyPair.getPrivate().getEncoded());
-		crypto.verifySignature(macKey, keyPair.getPublic().getEncoded(),
-				signature);
+		byte[] signature = crypto.sign(macKey, alice.getPrivateKey());
+		crypto.verifySignature(macKey, alice.getPublicKey(), signature);
 	}
 
 	@Test
 	public void testAliceActivateMac() throws Exception {
 		SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true);
 		byte[] aliceMac = crypto.activateMac(aliceMacKey);
-
 		crypto.verifyActivateMac(aliceMac, aliceMacKey);
 	}
 
@@ -151,7 +155,6 @@ public class IntroductionCryptoImplTest extends BrambleTestCase {
 	public void testBobActivateMac() throws Exception {
 		SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false);
 		byte[] bobMac = crypto.activateMac(bobMacKey);
-
 		crypto.verifyActivateMac(bobMac, bobMacKey);
 	}
 
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java
index a28e8321e2ae8adf1904a7495f8a7a2b0f28d680..f966aa2cb06531c1c3b241bdd112c10d23620f0b 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoTest.java
@@ -1,6 +1,5 @@
 package org.briarproject.briar.introduction;
 
-import org.briarproject.bramble.api.UniqueId;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.crypto.CryptoComponent;
 import org.briarproject.bramble.api.identity.Author;
@@ -10,7 +9,7 @@ import org.jmock.Expectations;
 import org.junit.Test;
 
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_SESSION_ID;
 import static org.junit.Assert.assertEquals;
 
@@ -25,7 +24,7 @@ public class IntroductionCryptoTest extends BrambleMockTestCase {
 
 	private final Author introducer = getAuthor();
 	private final Author alice = getAuthor(), bob = getAuthor();
-	private final byte[] hash = getRandomBytes(UniqueId.LENGTH);
+	private final byte[] hash = getRandomId();
 
 	@Test
 	public void testGetSessionId() {
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
index ab91ef92caf417a87eabc63af609091ead8c6e80..b692492f421fc94552fac3fa60540d16858d1c7e 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
@@ -634,7 +634,7 @@ public class IntroductionIntegrationTest
 
 		// fake a second ACCEPT message from introducee1
 		Message msg = c1.getMessageEncoder()
-				.encodeAcceptMessage(m.getGroupId(), clock.currentTimeMillis(),
+				.encodeAcceptMessage(m.getGroupId(), m.getTimestamp() + 1,
 						m.getMessageId(), m.getSessionId(),
 						m.getEphemeralPublicKey(), m.getAcceptTimestamp(),
 						m.getTransportProperties());
@@ -671,7 +671,7 @@ public class IntroductionIntegrationTest
 
 		// fake a second DECLINE message also from introducee1
 		Message msg = c1.getMessageEncoder()
-				.encodeDeclineMessage(m.getGroupId(), clock.currentTimeMillis(),
+				.encodeDeclineMessage(m.getGroupId(), m.getTimestamp() + 1,
 						m.getMessageId(), m.getSessionId());
 		c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true);
 
@@ -715,7 +715,7 @@ public class IntroductionIntegrationTest
 
 		// fake a second AUTH message also from introducee1
 		Message msg = c1.getMessageEncoder()
-				.encodeAuthMessage(m.getGroupId(), clock.currentTimeMillis(),
+				.encodeAuthMessage(m.getGroupId(), m.getTimestamp() + 1,
 						m.getMessageId(), m.getSessionId(), m.getMac(),
 						m.getSignature());
 		c1.getClientHelper().addLocalMessage(msg, new BdfDictionary(), true);
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java
index afd6d4394a696e738a1d61982794954f16bfe729..3a90d7d148af021f0744cf0a3f67502c324e2f78 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTestComponent.java
@@ -59,6 +59,10 @@ interface IntroductionIntegrationTestComponent
 
 	void inject(IntroductionIntegrationTest init);
 
+	void inject(MessageEncoderParserIntegrationTest init);
+	void inject(SessionEncoderParserIntegrationTest init);
+	void inject(IntroductionCryptoIntegrationTest init);
+
 	MessageEncoder getMessageEncoder();
 	MessageParser getMessageParser();
 	SessionParser getSessionParser();
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java
index 7614121cf54a19d6a5c23f553c6e9a163e923791..b1ad9caa9b6a92e7f11e8cfcad7ba867ebb4ed9b 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionValidatorTest.java
@@ -126,8 +126,8 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 				previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
 				acceptTimestamp, transportProperties);
 		context.checking(new Expectations() {{
-			oneOf(clientHelper).parseAndValidateTransportProperties(
-					transportProperties.getDictionary("transportId"));
+			oneOf(clientHelper).parseAndValidateTransportPropertiesMap(
+					transportProperties);
 		}});
 		expectEncodeMetadata(ACCEPT);
 		BdfMessageContext messageContext =
@@ -178,6 +178,14 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 		validator.validateMessage(message, group, body);
 	}
 
+	@Test(expected = FormatException.class)
+	public void testRejectsNegativeTimestampForAccept() throws Exception {
+		BdfList body = BdfList.of(ACCEPT.getValue(), sessionId.getBytes(),
+				previousMsgId.getBytes(), getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
+				-1, transportProperties);
+		validator.validateMessage(message, group, body);
+	}
+
 	@Test(expected = FormatException.class)
 	public void testRejectsEmptyTransportPropertiesForAccept()
 			throws Exception {
@@ -405,9 +413,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 
 	private void expectEncodeRequestMetadata() {
 		context.checking(new Expectations() {{
-			oneOf(messageEncoder)
-					.encodeRequestMetadata(message.getTimestamp(), false, false,
-							false);
+			oneOf(messageEncoder).encodeRequestMetadata(message.getTimestamp());
 			will(returnValue(meta));
 		}});
 	}
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java
index f3480f15832cb6b3d80edb2281ee9dca09a986bc..7b15b6ab43e665f52c99af222f833298a758c161 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderParserIntegrationTest.java
@@ -16,8 +16,6 @@ import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.system.Clock;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.briar.api.client.SessionId;
-import org.briarproject.briar.test.BriarIntegrationTestComponent;
-import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
 import org.junit.Test;
 
 import java.util.Map;
@@ -72,8 +70,8 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
 	private final byte[] signature = getRandomBytes(MAX_SIGNATURE_BYTES);
 
 	public MessageEncoderParserIntegrationTest() {
-		BriarIntegrationTestComponent component =
-				DaggerBriarIntegrationTestComponent.builder().build();
+		IntroductionIntegrationTestComponent component =
+				DaggerIntroductionIntegrationTestComponent.builder().build();
 		component.inject(this);
 
 		messageEncoder = new MessageEncoderImpl(clientHelper, messageFactory);
@@ -86,13 +84,13 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
 	@Test
 	public void testRequestMessageMetadata() throws FormatException {
 		BdfDictionary d = messageEncoder
-				.encodeRequestMetadata(timestamp, true, false, false);
+				.encodeRequestMetadata(timestamp);
 		MessageMetadata meta = messageParser.parseMetadata(d);
 
 		assertEquals(REQUEST, meta.getMessageType());
 		assertNull(meta.getSessionId());
 		assertEquals(timestamp, meta.getTimestamp());
-		assertTrue(meta.isLocal());
+		assertFalse(meta.isLocal());
 		assertFalse(meta.isRead());
 		assertFalse(meta.isVisibleInConversation());
 		assertFalse(meta.isAvailableToAnswer());
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java
index b1dae81c80b3f042f6ee3ab0e574648e65b1fd0f..56fe04bdb3219547caa2e01a80b1fbb0ccc94216 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/MessageEncoderTest.java
@@ -7,13 +7,12 @@ import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.bramble.api.sync.Message;
 import org.briarproject.bramble.api.sync.MessageFactory;
-import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.test.BrambleMockTestCase;
 import org.jmock.Expectations;
 import org.junit.Test;
 
 import static org.briarproject.bramble.test.TestUtils.getAuthor;
-import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
+import static org.briarproject.bramble.test.TestUtils.getMessage;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.util.StringUtils.getRandomString;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.MAX_REQUEST_MESSAGE_LENGTH;
@@ -28,11 +27,9 @@ public class MessageEncoderTest extends BrambleMockTestCase {
 			new MessageEncoderImpl(clientHelper, messageFactory);
 
 	private final GroupId groupId = new GroupId(getRandomId());
-	private final long timestamp = 42L;
-	private final Message message =
-			new Message(new MessageId(getRandomId()), groupId, timestamp,
-					getRandomBytes(48));
-	private final byte[] body = getRandomBytes(42);
+	private final Message message = getMessage(groupId);
+	private final long timestamp = message.getTimestamp();
+	private final byte[] body = message.getRaw();
 	private final Author author = getAuthor();
 	private final BdfList authorList = new BdfList();
 	private final String text = getRandomString(MAX_REQUEST_MESSAGE_LENGTH);
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java
index ffbcaa94369cd587837143c338c9198e7dfdbcbe..9ddb006323c4f5f28c9c36a59a80c53b9d1015fb 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/SessionEncoderParserIntegrationTest.java
@@ -14,8 +14,6 @@ import org.briarproject.bramble.api.transport.KeySetId;
 import org.briarproject.bramble.test.BrambleTestCase;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.introduction.IntroducerSession.Introducee;
-import org.briarproject.briar.test.BriarIntegrationTestComponent;
-import org.briarproject.briar.test.DaggerBriarIntegrationTestComponent;
 import org.junit.Test;
 
 import java.util.HashMap;
@@ -82,8 +80,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 	private final byte[] remoteMacKey = getRandomBytes(SecretKey.LENGTH);
 
 	public SessionEncoderParserIntegrationTest() {
-		BriarIntegrationTestComponent component =
-				DaggerBriarIntegrationTestComponent.builder().build();
+		IntroductionIntegrationTestComponent component =
+				DaggerIntroductionIntegrationTestComponent.builder().build();
 		component.inject(this);
 
 		sessionEncoder = new SessionEncoderImpl(clientHelper);
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
index f918b0a2d8fa02a9388ceaa920d2cfd5d890375c..ecaf60d1b9763e069018b243312948c3906dc80f 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarIntegrationTestComponent.java
@@ -9,7 +9,6 @@ import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.lifecycle.LifecycleManager;
 import org.briarproject.bramble.api.properties.TransportPropertyManager;
 import org.briarproject.bramble.api.sync.SyncSessionFactory;
-import org.briarproject.bramble.api.transport.KeyManager;
 import org.briarproject.bramble.client.ClientModule;
 import org.briarproject.bramble.contact.ContactModule;
 import org.briarproject.bramble.crypto.CryptoModule;
@@ -37,8 +36,8 @@ import org.briarproject.briar.api.privategroup.invitation.GroupInvitationManager
 import org.briarproject.briar.blog.BlogModule;
 import org.briarproject.briar.client.BriarClientModule;
 import org.briarproject.briar.forum.ForumModule;
+import org.briarproject.briar.introduction.IntroductionCryptoIntegrationTest;
 import org.briarproject.briar.introduction.IntroductionModule;
-import org.briarproject.briar.introduction.IntroductionCryptoImplTest;
 import org.briarproject.briar.introduction.MessageEncoderParserIntegrationTest;
 import org.briarproject.briar.introduction.SessionEncoderParserIntegrationTest;
 import org.briarproject.briar.messaging.MessagingModule;
@@ -80,10 +79,6 @@ public interface BriarIntegrationTestComponent {
 
 	void inject(BriarIntegrationTest<BriarIntegrationTestComponent> init);
 
-	void inject(MessageEncoderParserIntegrationTest init);
-	void inject(SessionEncoderParserIntegrationTest init);
-	void inject(IntroductionCryptoImplTest init);
-
 	void inject(BlogModule.EagerSingletons init);
 
 	void inject(ContactModule.EagerSingletons init);
@@ -146,8 +141,6 @@ public interface BriarIntegrationTestComponent {
 
 	TransportPropertyManager getTransportPropertyManager();
 
-	KeyManager getKeyManager();
-
 	AuthorFactory getAuthorFactory();
 
 	BlogFactory getBlogFactory();
diff --git a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java
index 5de39e9f56db3a8c6b2375e1b5b5e7a8654bdd72..9f71087ebd38fc5d2e3ce8883fc9b37c13c089ce 100644
--- a/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java
+++ b/briar-core/src/test/java/org/briarproject/briar/test/BriarTestUtils.java
@@ -1,8 +1,11 @@
 package org.briarproject.briar.test;
 
+import org.briarproject.bramble.api.crypto.CryptoComponent;
+import org.briarproject.bramble.api.crypto.KeyPair;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.AuthorFactory;
+import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.sync.GroupId;
 import org.briarproject.briar.api.client.MessageTracker;
 import org.briarproject.briar.api.client.MessageTracker.GroupCount;
@@ -35,4 +38,12 @@ public class BriarTestUtils {
 				getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
 	}
 
+	public static LocalAuthor getRealLocalAuthor(
+			CryptoComponent cryptoComponent, AuthorFactory authorFactory) {
+		KeyPair keyPair = cryptoComponent.generateSignatureKeyPair();
+		return authorFactory.createLocalAuthor(getRandomString(5),
+				keyPair.getPublic().getEncoded(),
+				keyPair.getPrivate().getEncoded());
+	}
+
 }