diff --git a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java
index 9b454218d5e736f5d17546e4bbe67775e40684f1..87681d525076125d90ed43e7437911d4aea8f97e 100644
--- a/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java
+++ b/briar-api/src/main/java/org/briarproject/briar/api/introduction/IntroductionConstants.java
@@ -26,4 +26,7 @@ public interface IntroductionConstants {
 
 	String LABEL_AUTH_NONCE = "org.briarproject.briar.introduction/AUTH_NONCE";
 
+	String LABEL_ACTIVATE_MAC =
+			"org.briarproject.briar.introduction/ACTIVATE_MAC";
+
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
index 300d3f6fb1e3f638066ada01a52c9599cc7ec1d8..75af1b87ca73cfe6e04a61506b709f282bee37e7 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/AbstractProtocolEngine.java
@@ -110,11 +110,11 @@ abstract class AbstractProtocolEngine<S extends Session>
 		return m;
 	}
 
-	Message sendActivateMessage(Transaction txn, PeerSession s, long timestamp)
-			throws DbException {
+	Message sendActivateMessage(Transaction txn, PeerSession s, long timestamp,
+			byte[] mac) throws DbException {
 		Message m = messageEncoder
 				.encodeActivateMessage(s.getContactGroupId(), timestamp,
-						s.getLastLocalMessageId(), s.getSessionId());
+						s.getLastLocalMessageId(), s.getSessionId(), mac);
 		sendMessage(txn, ACTIVATE, s.getSessionId(), m, false);
 		return m;
 	}
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java b/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java
index e7498dec2781c0ed36f8bebd8077ebabb47d2f5e..5f767737d90fe68029f80e2e0ab4d87932df13ce 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/ActivateMessage.java
@@ -12,15 +12,22 @@ import javax.annotation.concurrent.Immutable;
 class ActivateMessage extends AbstractIntroductionMessage {
 
 	private final SessionId sessionId;
+	private final byte[] mac;
 
 	protected ActivateMessage(MessageId messageId, GroupId groupId,
-			long timestamp, MessageId previousMessageId, SessionId sessionId) {
+			long timestamp, MessageId previousMessageId, SessionId sessionId,
+			byte[] mac) {
 		super(messageId, groupId, timestamp, previousMessageId);
 		this.sessionId = sessionId;
+		this.mac = mac;
 	}
 
 	public SessionId getSessionId() {
 		return sessionId;
 	}
 
+	public byte[] getMac() {
+		return mac;
+	}
+
 }
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 44714170064ecda2ff3afa2b60d51577292cbf67..ce7d5f76c2c45f2268aa9624f7c8336ab790c76c 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
@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.FormatException;
 import org.briarproject.bramble.api.client.ClientHelper;
 import org.briarproject.bramble.api.client.ContactGroupFactory;
 import org.briarproject.bramble.api.contact.Contact;
-import org.briarproject.bramble.api.contact.ContactId;
 import org.briarproject.bramble.api.contact.ContactManager;
 import org.briarproject.bramble.api.crypto.KeyPair;
 import org.briarproject.bramble.api.crypto.SecretKey;
@@ -13,7 +12,6 @@ import org.briarproject.bramble.api.db.ContactExistsException;
 import org.briarproject.bramble.api.db.DatabaseComponent;
 import org.briarproject.bramble.api.db.DbException;
 import org.briarproject.bramble.api.db.Transaction;
-import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.identity.IdentityManager;
 import org.briarproject.bramble.api.identity.LocalAuthor;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -354,7 +352,7 @@ class IntroduceeProtocolEngine
 		IntroductionResponse request =
 				new IntroductionResponse(s.getSessionId(), m.getMessageId(),
 						m.getGroupId(), INTRODUCEE, m.getTimestamp(), false,
-						false, false, false, s.getRemoteAuthor().getName(),
+						false, false, false, s.getRemote().author.getName(),
 						false);
 		IntroductionResponseReceivedEvent e =
 				new IntroductionResponseReceivedEvent(c.getId(), request);
@@ -383,16 +381,18 @@ class IntroduceeProtocolEngine
 
 	private IntroduceeSession onLocalAuth(Transaction txn, IntroduceeSession s)
 			throws DbException {
-		boolean alice = isAlice(txn, s);
 		byte[] mac;
 		byte[] signature;
-		SecretKey masterKey;
+		SecretKey masterKey, aliceMacKey, bobMacKey;
 		try {
-			masterKey = crypto.deriveMasterKey(s, alice);
-			SecretKey macKey = crypto.deriveMacKey(masterKey, alice);
+			masterKey = crypto.deriveMasterKey(s);
+			aliceMacKey = crypto.deriveMacKey(masterKey, true);
+			bobMacKey = crypto.deriveMacKey(masterKey, false);
+			SecretKey ourMacKey = s.getLocal().alice ? aliceMacKey : bobMacKey;
 			LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
-			mac = crypto.mac(macKey, s, localAuthor.getId(), alice);
-			signature = crypto.sign(macKey, localAuthor.getPrivateKey());
+			mac = crypto.authMac(ourMacKey, s, localAuthor.getId(),
+					s.getLocal().alice);
+			signature = crypto.sign(ourMacKey, localAuthor.getPrivateKey());
 		} catch (GeneralSecurityException e) {
 			// TODO
 			return abort(txn, s);
@@ -400,7 +400,8 @@ class IntroduceeProtocolEngine
 		if (s.getState() != AWAIT_AUTH) throw new AssertionError();
 		Message sent = sendAuthMessage(txn, s, getLocalTimestamp(s), mac,
 				signature);
-		return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, masterKey, sent);
+		return IntroduceeSession.addLocalAuth(s, AWAIT_AUTH, sent, masterKey,
+				aliceMacKey, bobMacKey);
 	}
 
 	private IntroduceeSession onRemoteAuth(Transaction txn,
@@ -411,33 +412,50 @@ class IntroduceeProtocolEngine
 
 		LocalAuthor localAuthor = identityManager.getLocalAuthor(txn);
 		try {
-			crypto.verifyMac(m.getMac(), s, localAuthor.getId());
+			crypto.verifyAuthMac(m.getMac(), s, localAuthor.getId());
 			crypto.verifySignature(m.getSignature(), s, localAuthor.getId());
 		} catch (GeneralSecurityException e) {
 			return abort(txn, s);
 		}
-		long timestamp =
-				Math.min(s.getAcceptTimestamp(), s.getRemoteAcceptTimestamp());
+		long timestamp = Math.min(s.getLocal().acceptTimestamp,
+				s.getRemote().acceptTimestamp);
 		if (timestamp == -1) throw new AssertionError();
 
-		Map<TransportId, KeySetId> keys = null;
+		boolean contactAdded = false;
 		try {
-			ContactId c = contactManager
-					.addContact(txn, s.getRemoteAuthor(), localAuthor.getId(),
-							false, false);
-			if (s.getRemoteTransportProperties() == null ||
-					s.getMasterKey() == null) throw new AssertionError();
-			transportPropertyManager.addRemoteProperties(txn, c,
-					s.getRemoteTransportProperties());
-			keys = keyManager
-					.addUnboundKeys(txn, new SecretKey(s.getMasterKey()),
-							timestamp, isAlice(txn, s));
+			contactManager
+					.addContact(txn, s.getRemote().author, localAuthor.getId(),
+							false, true);
+			contactAdded = true;
 		} catch (ContactExistsException e) {
-			// Ignore this and continue without adding transport properties
-			// or unbound transport keys. Continue with keys as null.
+			// Ignore this, because the other introducee might have deleted us.
+			// So we still want updated transport properties
+			// and new transport keys.
 		}
+		Contact c = contactManager.getContact(txn, s.getRemote().author.getId(),
+				localAuthor.getId());
+
+		// bind the keys to the new (or existing) contact
+		//noinspection ConstantConditions
+		Map<TransportId, KeySetId> keys = keyManager
+				.addUnboundKeys(txn, new SecretKey(s.getMasterKey()),
+						timestamp, s.getRemote().alice);
+		keyManager.bindKeys(txn, c.getId(), keys);
+
+		// add signed transport properties for the contact
+		//noinspection ConstantConditions
+		transportPropertyManager.addRemoteProperties(txn, c.getId(),
+				s.getRemote().transportProperties);
 
-		Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s));
+		// send ACTIVATE message with a MAC
+		byte[] mac = crypto.activateMac(s);
+		Message sent = sendActivateMessage(txn, s, getLocalTimestamp(s), mac);
+
+		if (contactAdded) {
+			// Broadcast IntroductionSucceededEvent, because contact got added
+			IntroductionSucceededEvent e = new IntroductionSucceededEvent(c);
+			txn.attach(e);
+		}
 
 		// Move to AWAIT_ACTIVATE state and clear key material from session
 		return IntroduceeSession.awaitActivate(s, m, sent, keys);
@@ -449,23 +467,17 @@ class IntroduceeProtocolEngine
 		if (isInvalidDependency(s, m.getPreviousMessageId()))
 			return abort(txn, s);
 
-		// Only bind keys if contact did not exist during AUTH
-		if (s.getTransportKeys() != null) {
-			Contact c =
-					contactManager.getContact(txn, s.getRemoteAuthor().getId(),
-							identityManager.getLocalAuthor(txn).getId());
-			keyManager.bindKeys(txn, c.getId(), s.getTransportKeys());
-			keyManager.activateKeys(txn, s.getTransportKeys());
-
-			// TODO remove when concept of inactive contacts is removed
-			contactManager.setContactActive(txn, c.getId(), true);
-
-			// TODO move this to AUTH step when concept of inactive contacts is removed
-			// Broadcast IntroductionSucceededEvent
-			IntroductionSucceededEvent e = new IntroductionSucceededEvent(c);
-			txn.attach(e);
+		// Validate MAC
+		try {
+			crypto.verifyActivateMac(m.getMac(), s);
+		} catch (GeneralSecurityException e) {
+			// TODO remove transport keys?
+			return abort(txn, s);
 		}
 
+		// Activate transport keys
+		keyManager.activateKeys(txn, s.getTransportKeys());
+
 		// Move back to START state
 		return IntroduceeSession
 				.clear(s, s.getLastLocalMessageId(), s.getLocalTimestamp(),
@@ -513,12 +525,6 @@ class IntroduceeProtocolEngine
 				s.getRequestTimestamp());
 	}
 
-	private boolean isAlice(Transaction txn, IntroduceeSession s)
-			throws DbException {
-		Author localAuthor = identityManager.getLocalAuthor(txn);
-		return crypto.isAlice(localAuthor.getId(), s.getRemoteAuthor().getId());
-	}
-
 	private void addSessionId(Transaction txn, MessageId m, SessionId sessionId)
 			throws DbException {
 		BdfDictionary meta = new BdfDictionary();
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java
index 1aadc39ad564fe22c44f6a111fda939f3146ee6d..92b06abc05a36f5f50c714850a9dfe74befe2c33 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroduceeSession.java
@@ -27,68 +27,44 @@ class IntroduceeSession extends Session<IntroduceeState>
 		implements PeerSession {
 
 	private final GroupId contactGroupId;
-	private final long localTimestamp, acceptTimestamp, remoteAcceptTimestamp;
+	private final Author introducer;
+	private final Local local;
+	private final Remote remote;
 	@Nullable
-	private final MessageId lastLocalMessageId, lastRemoteMessageId;
-	private final Author introducer, remoteAuthor;
-	@Nullable
-	private final byte[] ephemeralPublicKey, ephemeralPrivateKey;
-	@Nullable
-	private final byte[] masterKey, remoteEphemeralPublicKey;
-	@Nullable
-	private final Map<TransportId, TransportProperties> transportProperties;
-	@Nullable
-	private final Map<TransportId, TransportProperties>
-			remoteTransportProperties;
+	private final byte[] masterKey;
 	@Nullable
 	private final Map<TransportId, KeySetId> transportKeys;
 
 	IntroduceeSession(SessionId sessionId, IntroduceeState state,
-			long requestTimestamp, GroupId contactGroupId,
-			@Nullable MessageId lastLocalMessageId, long localTimestamp,
-			@Nullable MessageId lastRemoteMessageId, Author introducer,
-			@Nullable byte[] ephemeralPublicKey,
-			@Nullable byte[] ephemeralPrivateKey,
-			@Nullable Map<TransportId, TransportProperties> transportProperties,
-			long acceptTimestamp, @Nullable byte[] masterKey,
-			Author remoteAuthor,
-			@Nullable byte[] remoteEphemeralPublicKey, @Nullable
-			Map<TransportId, TransportProperties> remoteTransportProperties,
-			long remoteAcceptTimestamp,
+			long requestTimestamp, GroupId contactGroupId, Author introducer,
+			Local local, Remote remote, @Nullable byte[] masterKey,
 			@Nullable Map<TransportId, KeySetId> transportKeys) {
 		super(sessionId, state, requestTimestamp);
 		this.contactGroupId = contactGroupId;
-		this.lastLocalMessageId = lastLocalMessageId;
-		this.localTimestamp = localTimestamp;
-		this.lastRemoteMessageId = lastRemoteMessageId;
 		this.introducer = introducer;
-		this.ephemeralPublicKey = ephemeralPublicKey;
-		this.ephemeralPrivateKey = ephemeralPrivateKey;
-		this.transportProperties = transportProperties;
-		this.acceptTimestamp = acceptTimestamp;
+		this.local = local;
+		this.remote = remote;
 		this.masterKey = masterKey;
-		this.remoteAuthor = remoteAuthor;
-		this.remoteEphemeralPublicKey = remoteEphemeralPublicKey;
-		this.remoteTransportProperties = remoteTransportProperties;
-		this.remoteAcceptTimestamp = remoteAcceptTimestamp;
 		this.transportKeys = transportKeys;
 	}
 
 	static IntroduceeSession getInitial(GroupId contactGroupId,
-			SessionId sessionId, Author introducer, Author remoteAuthor) {
-		return new IntroduceeSession(sessionId, START, -1, contactGroupId, null,
-				-1, null, introducer, null, null, null, -1, null, remoteAuthor,
-				null, null, -1, null);
+			SessionId sessionId, Author introducer, boolean localIsAlice,
+			Author remoteAuthor) {
+		Local local =
+				new Local(localIsAlice, null, -1, null, null, null, -1, null);
+		Remote remote =
+				new Remote(!localIsAlice, remoteAuthor, null, null, null, -1,
+						null);
+		return new IntroduceeSession(sessionId, START, -1, contactGroupId,
+				introducer, local, remote, null, null);
 	}
 
 	static IntroduceeSession addRemoteRequest(IntroduceeSession s,
 			IntroduceeState state, RequestMessage m) {
+		Remote remote = new Remote(s.remote, m.getMessageId());
 		return new IntroduceeSession(s.getSessionId(), state, m.getTimestamp(),
-				s.contactGroupId, s.lastLocalMessageId, s.localTimestamp,
-				m.getMessageId(), s.introducer, s.ephemeralPublicKey,
-				s.ephemeralPrivateKey, s.transportProperties, s.acceptTimestamp,
-				s.masterKey, s.remoteAuthor, s.remoteEphemeralPublicKey,
-				s.remoteTransportProperties, s.remoteAcceptTimestamp,
+				s.contactGroupId, s.introducer, s.local, remote, s.masterKey,
 				s.transportKeys);
 	}
 
@@ -97,57 +73,66 @@ class IntroduceeSession extends Session<IntroduceeState>
 			byte[] ephemeralPublicKey, byte[] ephemeralPrivateKey,
 			long acceptTimestamp,
 			Map<TransportId, TransportProperties> transportProperties) {
+		Local local = new Local(s.local.alice, acceptMessage.getId(),
+				acceptMessage.getTimestamp(), ephemeralPublicKey,
+				ephemeralPrivateKey, transportProperties, acceptTimestamp,
+				null);
 		return new IntroduceeSession(s.getSessionId(), state,
-				s.getRequestTimestamp(), s.contactGroupId,
-				acceptMessage.getId(), acceptMessage.getTimestamp(),
-				s.lastRemoteMessageId, s.introducer, ephemeralPublicKey,
-				ephemeralPrivateKey, transportProperties,
-				acceptTimestamp, s.masterKey, s.remoteAuthor,
-				s.remoteEphemeralPublicKey, s.remoteTransportProperties,
-				s.remoteAcceptTimestamp, s.transportKeys);
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				s.remote, s.masterKey, s.transportKeys);
 	}
 
 	static IntroduceeSession addRemoteAccept(IntroduceeSession s,
-			IntroduceeState state, AcceptMessage acceptMessage) {
+			IntroduceeState state, AcceptMessage m) {
+		Remote remote =
+				new Remote(s.remote.alice, s.remote.author, m.getMessageId(),
+						m.getEphemeralPublicKey(), m.getTransportProperties(),
+						m.getAcceptTimestamp(), s.remote.macKey);
 		return new IntroduceeSession(s.getSessionId(), state,
-				s.getRequestTimestamp(), s.contactGroupId, s.lastLocalMessageId,
-				s.localTimestamp, acceptMessage.getMessageId(), s.introducer,
-				s.ephemeralPublicKey, s.ephemeralPrivateKey,
-				s.transportProperties, s.acceptTimestamp, s.masterKey,
-				s.remoteAuthor, acceptMessage.getEphemeralPublicKey(),
-				acceptMessage.getTransportProperties(),
-				acceptMessage.getAcceptTimestamp(), s.transportKeys);
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer,
+				s.local, remote, s.masterKey, s.transportKeys);
 	}
 
 	static IntroduceeSession addLocalAuth(IntroduceeSession s,
-			IntroduceeState state, SecretKey masterKey, Message m) {
+			IntroduceeState state, Message m, SecretKey masterKey,
+			SecretKey aliceMacKey, SecretKey bobMacKey) {
+		// add mac key and sent message
+		Local local = new Local(s.local.alice, m.getId(), m.getTimestamp(),
+				s.local.ephemeralPublicKey, s.local.ephemeralPrivateKey,
+				s.local.transportProperties, s.local.acceptTimestamp,
+				s.local.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes());
+		// just add the mac key
+		Remote remote = new Remote(s.remote.alice, s.remote.author,
+				s.remote.lastMessageId, s.remote.ephemeralPublicKey,
+				s.remote.transportProperties, s.remote.acceptTimestamp,
+				s.remote.alice ? aliceMacKey.getBytes() : bobMacKey.getBytes());
+		// add master key
 		return new IntroduceeSession(s.getSessionId(), state,
-				s.getRequestTimestamp(), s.contactGroupId, m.getId(),
-				m.getTimestamp(), s.lastRemoteMessageId, s.introducer,
-				s.ephemeralPublicKey, s.ephemeralPrivateKey,
-				s.transportProperties, s.acceptTimestamp, masterKey.getBytes(),
-				s.remoteAuthor, s.remoteEphemeralPublicKey,
-				s.remoteTransportProperties, s.remoteAcceptTimestamp,
-				s.transportKeys);
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				remote, masterKey.getBytes(), s.transportKeys);
 	}
 
 	static IntroduceeSession awaitActivate(IntroduceeSession s, AuthMessage m,
 			Message sent, @Nullable Map<TransportId, KeySetId> transportKeys) {
+		Local local = new Local(s.local, sent.getId(), sent.getTimestamp());
+		Remote remote = new Remote(s.remote, m.getMessageId());
 		return new IntroduceeSession(s.getSessionId(), AWAIT_ACTIVATE,
-				s.getRequestTimestamp(), s.contactGroupId, sent.getId(),
-				sent.getTimestamp(), m.getMessageId(), s.introducer, null, null,
-				null, s.acceptTimestamp, null, s.getRemoteAuthor(), null, null,
-				s.remoteAcceptTimestamp, transportKeys);
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				remote, null, transportKeys);
 	}
 
 	static IntroduceeSession clear(IntroduceeSession s,
 			@Nullable MessageId lastLocalMessageId, long localTimestamp,
 			@Nullable MessageId lastRemoteMessageId) {
+		Local local =
+				new Local(s.local.alice, lastLocalMessageId, localTimestamp,
+						null, null, null, -1, null);
+		Remote remote =
+				new Remote(s.remote.alice, s.remote.author, lastRemoteMessageId,
+						null, null, -1, null);
 		return new IntroduceeSession(s.getSessionId(), START,
-				s.getRequestTimestamp(), s.getContactGroupId(),
-				lastLocalMessageId, localTimestamp, lastRemoteMessageId,
-				s.getIntroducer(), null, null, null, -1, null,
-				s.getRemoteAuthor(), null, null, -1, null);
+				s.getRequestTimestamp(), s.contactGroupId, s.introducer, local,
+				remote, null, null);
 	}
 
 	@Override
@@ -155,45 +140,38 @@ class IntroduceeSession extends Session<IntroduceeState>
 		return INTRODUCEE;
 	}
 
+	@Override
 	public GroupId getContactGroupId() {
 		return contactGroupId;
 	}
 
+	@Override
 	public long getLocalTimestamp() {
-		return localTimestamp;
+		return local.lastMessageTimestamp;
 	}
 
 	@Nullable
+	@Override
 	public MessageId getLastLocalMessageId() {
-		return lastLocalMessageId;
+		return local.lastMessageId;
 	}
 
 	@Nullable
+	@Override
 	public MessageId getLastRemoteMessageId() {
-		return lastRemoteMessageId;
+		return remote.lastMessageId;
 	}
 
 	Author getIntroducer() {
 		return introducer;
 	}
 
-	@Nullable
-	byte[] getEphemeralPublicKey() {
-		return ephemeralPublicKey;
+	public Local getLocal() {
+		return local;
 	}
 
-	@Nullable
-	byte[] getEphemeralPrivateKey() {
-		return ephemeralPrivateKey;
-	}
-
-	@Nullable
-	Map<TransportId, TransportProperties> getTransportProperties() {
-		return transportProperties;
-	}
-
-	long getAcceptTimestamp() {
-		return acceptTimestamp;
+	public Remote getRemote() {
+		return remote;
 	}
 
 	@Nullable
@@ -201,27 +179,77 @@ class IntroduceeSession extends Session<IntroduceeState>
 		return masterKey;
 	}
 
-	Author getRemoteAuthor() {
-		return remoteAuthor;
-	}
-
-	@Nullable
-	byte[] getRemotePublicKey() {
-		return remoteEphemeralPublicKey;
-	}
-
-	@Nullable
-	Map<TransportId, TransportProperties> getRemoteTransportProperties() {
-		return remoteTransportProperties;
-	}
-
-	long getRemoteAcceptTimestamp() {
-		return remoteAcceptTimestamp;
-	}
-
 	@Nullable
 	Map<TransportId, KeySetId> getTransportKeys() {
 		return transportKeys;
 	}
 
+	abstract static class Common {
+		final boolean alice;
+		@Nullable
+		final MessageId lastMessageId;
+		@Nullable
+		final byte[] ephemeralPublicKey;
+		@Nullable
+		final Map<TransportId, TransportProperties> transportProperties;
+		final long acceptTimestamp;
+		@Nullable
+		final byte[] macKey;
+
+		private Common(boolean alice, @Nullable MessageId lastMessageId,
+				@Nullable byte[] ephemeralPublicKey, @Nullable
+				Map<TransportId, TransportProperties> transportProperties,
+				long acceptTimestamp, @Nullable byte[] macKey) {
+			this.alice = alice;
+			this.lastMessageId = lastMessageId;
+			this.ephemeralPublicKey = ephemeralPublicKey;
+			this.transportProperties = transportProperties;
+			this.acceptTimestamp = acceptTimestamp;
+			this.macKey = macKey;
+		}
+	}
+
+	static class Local extends Common {
+		final long lastMessageTimestamp;
+		@Nullable
+		final byte[] ephemeralPrivateKey;
+
+		Local(boolean alice, @Nullable MessageId lastMessageId,
+				long lastMessageTimestamp, @Nullable byte[] ephemeralPublicKey,
+				@Nullable byte[] ephemeralPrivateKey, @Nullable
+				Map<TransportId, TransportProperties> transportProperties,
+				long acceptTimestamp, @Nullable byte[] macKey) {
+			super(alice, lastMessageId, ephemeralPublicKey, transportProperties,
+					acceptTimestamp, macKey);
+			this.lastMessageTimestamp = lastMessageTimestamp;
+			this.ephemeralPrivateKey = ephemeralPrivateKey;
+		}
+
+		private Local(Local s, @Nullable MessageId lastMessageId,
+				long lastMessageTimestamp) {
+			this(s.alice, lastMessageId, lastMessageTimestamp,
+					s.ephemeralPublicKey, s.ephemeralPrivateKey,
+					s.transportProperties, s.acceptTimestamp, s.macKey);
+		}
+	}
+
+	static class Remote extends Common {
+		final Author author;
+
+		Remote(boolean alice, Author author,
+				@Nullable MessageId lastMessageId,
+				@Nullable byte[] ephemeralPublicKey, @Nullable
+				Map<TransportId, TransportProperties> transportProperties,
+				long acceptTimestamp, @Nullable byte[] macKey) {
+			super(alice, lastMessageId, ephemeralPublicKey, transportProperties,
+					acceptTimestamp, macKey);
+			this.author = author;
+		}
+
+		private Remote(Remote s, @Nullable MessageId lastMessageId) {
+			this(s.alice, s.author, lastMessageId, s.ephemeralPublicKey,
+					s.transportProperties, s.acceptTimestamp, s.macKey);
+		}
+	}
+
 }
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 2fbe0cab8b11e92ea60e2b702ca939b1476f763a..7115ec089232ddfda5cdd56f25b3aa540c67713a 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
@@ -440,7 +440,7 @@ class IntroducerProtocolEngine
 		// Forward ACTIVATE message
 		Introducee i = getOtherIntroducee(s, m.getGroupId());
 		long timestamp = getLocalTimestamp(s, i);
-		Message sent = sendActivateMessage(txn, i, timestamp);
+		Message sent = sendActivateMessage(txn, i, timestamp, m.getMac());
 
 		// Move to the next state
 		IntroducerState state = START;
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java
index afb1ff3e70ef31e643b99f61bb6e2e3283a65636..522759a55bcafa40e32b2cbe0efa691064d19893 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/IntroductionConstants.java
@@ -30,16 +30,19 @@ interface IntroductionConstants {
 
 	// Session Keys Introducee
 	String SESSION_KEY_INTRODUCER = "introducer";
+	String SESSION_KEY_LOCAL = "local";
+	String SESSION_KEY_REMOTE = "remote";
+
+	String SESSION_KEY_MASTER_KEY = "masterKey";
+	String SESSION_KEY_TRANSPORT_KEYS = "transportKeys";
+
+	String SESSION_KEY_ALICE = "alice";
 	String SESSION_KEY_EPHEMERAL_PUBLIC_KEY = "ephemeralPublicKey";
 	String SESSION_KEY_EPHEMERAL_PRIVATE_KEY = "ephemeralPrivateKey";
 	String SESSION_KEY_TRANSPORT_PROPERTIES = "transportProperties";
 	String SESSION_KEY_ACCEPT_TIMESTAMP = "acceptTimestamp";
-	String SESSION_KEY_MASTER_KEY = "masterKey";
+	String SESSION_KEY_MAC_KEY = "macKey";
+
 	String SESSION_KEY_REMOTE_AUTHOR = "remoteAuthor";
-	String SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY = "remoteEphemeralPublicKey";
-	String SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES =
-			"remoteTransportProperties";
-	String SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP = "remoteAcceptTimestamp";
-	String SESSION_KEY_TRANSPORT_KEYS = "transportKeys";
 
 }
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 3573f348a7f9911fd84db234fe5be9bad58d48ff..7504270d91d871f26c6a53530e6c5d56044ed390 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
@@ -30,16 +30,15 @@ interface IntroductionCrypto {
 	/**
 	 * Derives a session master key for Alice or Bob.
 	 *
-	 * @param alice true if the session owner is Alice
 	 * @return The secret master key
 	 */
-	SecretKey deriveMasterKey(IntroduceeSession s, boolean alice)
+	SecretKey deriveMasterKey(IntroduceeSession s)
 			throws GeneralSecurityException;
 
 	/**
 	 * Derives a MAC key from the session's master key for Alice or Bob.
 	 *
-	 * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession, boolean)}
+	 * @param masterKey The key returned by {@link #deriveMasterKey(IntroduceeSession)}
 	 * @param alice true for Alice's MAC key, false for Bob's
 	 * @return The MAC key
 	 */
@@ -49,17 +48,17 @@ interface IntroductionCrypto {
 	 * Generates a MAC that covers both introducee's ephemeral public keys,
 	 * transport properties, Author IDs and timestamps of the accept message.
 	 */
-	byte[] mac(SecretKey macKey, IntroduceeSession s, AuthorId localAuthorId,
-			boolean alice);
+	byte[] authMac(SecretKey macKey, IntroduceeSession s,
+			AuthorId localAuthorId, boolean alice);
 
 	/**
 	 * Verifies a received MAC
 	 *
 	 * @param mac The MAC to verify
-	 * as returned by {@link #deriveMasterKey(IntroduceeSession, boolean)}
+	 * as returned by {@link #deriveMasterKey(IntroduceeSession)}
 	 * @throws GeneralSecurityException if the verification fails
 	 */
-	void verifyMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId)
+	void verifyAuthMac(byte[] mac, IntroduceeSession s, AuthorId localAuthorId)
 			throws GeneralSecurityException;
 
 	/**
@@ -82,4 +81,17 @@ interface IntroductionCrypto {
 	void verifySignature(byte[] signature, IntroduceeSession s,
 			AuthorId localAuthorId) throws GeneralSecurityException;
 
+	/**
+	 * Generates a MAC using the local MAC key.
+	 */
+	byte[] activateMac(IntroduceeSession s);
+
+	/**
+	 * Verifies a MAC from an ACTIVATE message.
+	 *
+	 * @throws GeneralSecurityException if the verification fails
+	 */
+	void verifyActivateMac(byte[] mac, IntroduceeSession s)
+			throws GeneralSecurityException;
+
 }
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 18e2cbe1200440ff14c52e572d3167a0a0daaeef..6712a3153aae4b214aca863180647b5ca1e82833 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
@@ -23,6 +23,7 @@ import java.util.Map;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ACTIVATE_MAC;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_ALICE_MAC_KEY;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_MAC;
 import static org.briarproject.briar.api.introduction.IntroductionConstants.LABEL_AUTH_NONCE;
@@ -74,10 +75,14 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public SecretKey deriveMasterKey(IntroduceeSession s, boolean alice)
+	public SecretKey deriveMasterKey(IntroduceeSession s)
 			throws GeneralSecurityException {
-		return deriveMasterKey(s.getEphemeralPublicKey(),
-				s.getEphemeralPrivateKey(), s.getRemotePublicKey(), alice);
+		return deriveMasterKey(
+				s.getLocal().ephemeralPublicKey,
+				s.getLocal().ephemeralPrivateKey,
+				s.getRemote().ephemeralPublicKey,
+				s.getLocal().alice
+		);
 	}
 
 	SecretKey deriveMasterKey(byte[] publicKey, byte[] privateKey,
@@ -108,16 +113,17 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public byte[] mac(SecretKey macKey, IntroduceeSession s,
+	public byte[] authMac(SecretKey macKey, IntroduceeSession s,
 			AuthorId localAuthorId, boolean alice) {
-		return mac(macKey, s.getIntroducer().getId(), localAuthorId,
-				s.getRemoteAuthor().getId(), s.getAcceptTimestamp(),
-				s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(),
-				s.getRemotePublicKey(), s.getTransportProperties(),
-				s.getRemoteTransportProperties(), alice);
+		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);
 	}
 
-	byte[] mac(SecretKey macKey, AuthorId introducerId,
+	byte[] authMac(SecretKey macKey, AuthorId introducerId,
 			AuthorId localAuthorId, AuthorId remoteAuthorId,
 			long acceptTimestamp, long remoteAcceptTimestamp,
 			byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey,
@@ -125,7 +131,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 			Map<TransportId, TransportProperties> remoteTransportProperties,
 			boolean alice) {
 		byte[] inputs =
-				getMacInputs(introducerId, localAuthorId, remoteAuthorId,
+				getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId,
 						acceptTimestamp, remoteAcceptTimestamp,
 						ephemeralPublicKey, remoteEphemeralPublicKey,
 						transportProperties, remoteTransportProperties, alice);
@@ -138,19 +144,20 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 
 	@Override
 	@SuppressWarnings("ConstantConditions")
-	public void verifyMac(byte[] mac, IntroduceeSession s,
+	public void verifyAuthMac(byte[] mac, IntroduceeSession s,
 			AuthorId localAuthorId)
 			throws GeneralSecurityException {
-		boolean alice = isAlice(localAuthorId, s.getRemoteAuthor().getId());
-		verifyMac(mac, new SecretKey(s.getMasterKey()),
+		boolean alice = isAlice(localAuthorId, s.getRemote().author.getId());
+		verifyAuthMac(mac, new SecretKey(s.getRemote().macKey),
 				s.getIntroducer().getId(), localAuthorId,
-				s.getRemoteAuthor().getId(), s.getAcceptTimestamp(),
-				s.getRemoteAcceptTimestamp(), s.getEphemeralPublicKey(),
-				s.getRemotePublicKey(), s.getTransportProperties(),
-				s.getRemoteTransportProperties(), !alice);
+				s.getRemote().author.getId(), s.getLocal().acceptTimestamp,
+				s.getRemote().acceptTimestamp, s.getLocal().ephemeralPublicKey,
+				s.getRemote().ephemeralPublicKey,
+				s.getLocal().transportProperties,
+				s.getRemote().transportProperties, !alice);
 	}
 
-	void verifyMac(byte[] mac, SecretKey masterKey,
+	void verifyAuthMac(byte[] mac, SecretKey macKey,
 			AuthorId introducerId, AuthorId localAuthorId,
 			AuthorId remoteAuthorId, long acceptTimestamp,
 			long remoteAcceptTimestamp, byte[] ephemeralPublicKey,
@@ -158,9 +165,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 			Map<TransportId, TransportProperties> transportProperties,
 			Map<TransportId, TransportProperties> remoteTransportProperties,
 			boolean alice) throws GeneralSecurityException {
-		SecretKey macKey = deriveMacKey(masterKey, alice);
 		byte[] inputs =
-				getMacInputs(introducerId, localAuthorId, remoteAuthorId,
+				getAuthMacInputs(introducerId, localAuthorId, remoteAuthorId,
 						acceptTimestamp, remoteAcceptTimestamp,
 						ephemeralPublicKey, remoteEphemeralPublicKey,
 						transportProperties, remoteTransportProperties, !alice);
@@ -169,7 +175,7 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 		}
 	}
 
-	private byte[] getMacInputs(AuthorId introducerId,
+	private byte[] getAuthMacInputs(AuthorId introducerId,
 			AuthorId localAuthorId, AuthorId remoteAuthorId,
 			long acceptTimestamp, long remoteAcceptTimestamp,
 			byte[] ephemeralPublicKey, byte[] remoteEphemeralPublicKey,
@@ -214,9 +220,8 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 	@SuppressWarnings("ConstantConditions")
 	public void verifySignature(byte[] signature, IntroduceeSession s,
 			AuthorId localAuthorId) throws GeneralSecurityException {
-		boolean alice = isAlice(s.getRemoteAuthor().getId(), localAuthorId);
-		SecretKey macKey = deriveMacKey(new SecretKey(s.getMasterKey()), alice);
-		verifySignature(macKey, s.getRemoteAuthor().getPublicKey(), signature);
+		SecretKey macKey = new SecretKey(s.getRemote().macKey);
+		verifySignature(macKey, s.getRemote().author.getPublicKey(), signature);
 	}
 
 	void verifySignature(SecretKey macKey, byte[] publicKey,
@@ -232,4 +237,33 @@ class IntroductionCryptoImpl implements IntroductionCrypto {
 		return crypto.mac(LABEL_AUTH_NONCE, macKey);
 	}
 
+	@Override
+	public byte[] activateMac(IntroduceeSession s) {
+		if (s.getLocal().macKey == null)
+			throw new AssertionError("Local MAC key is null");
+		return activateMac(new SecretKey(s.getLocal().macKey));
+	}
+
+	byte[] activateMac(SecretKey macKey) {
+		return crypto.mac(
+				LABEL_ACTIVATE_MAC,
+				macKey
+		);
+	}
+
+	@Override
+	public void verifyActivateMac(byte[] mac, IntroduceeSession s)
+			throws GeneralSecurityException {
+		if (s.getRemote().macKey == null)
+			throw new AssertionError("Remote MAC key is null");
+		verifyActivateMac(mac, new SecretKey(s.getRemote().macKey));
+	}
+
+	void verifyActivateMac(byte[] mac, SecretKey macKey)
+			throws GeneralSecurityException {
+		if (!crypto.verifyMac(mac, LABEL_ACTIVATE_MAC, macKey)) {
+			throw new GeneralSecurityException();
+		}
+	}
+
 }
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 959d1d92bda4d295ac4ae54a38f450ee075602c4..d14bd46c70c5fab3317c21b5212c4da7a26d1790 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
@@ -190,8 +190,10 @@ class IntroductionManagerImpl extends ConversationClientImpl
 		Author remote = messageParser.parseRequestMessage(m, body).getAuthor();
 		if (local.equals(remote)) throw new FormatException();
 		SessionId sessionId = crypto.getSessionId(introducer, local, remote);
+		boolean alice = crypto.isAlice(local.getId(), remote.getId());
 		return IntroduceeSession
-				.getInitial(m.getGroupId(), sessionId, introducer, remote);
+				.getInitial(m.getGroupId(), sessionId, introducer, alice,
+						remote);
 	}
 
 	private <S extends Session> S handleMessage(Transaction txn, Message m,
@@ -441,7 +443,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 			IntroduceeSession session = sessionParser
 					.parseIntroduceeSession(contactGroupId, bdfSession);
 			sessionId = session.getSessionId();
-			author = session.getRemoteAuthor();
+			author = session.getRemote().author;
 		} else throw new AssertionError();
 		Message msg = clientHelper.getMessage(txn, m);
 		BdfList body = clientHelper.getMessageAsList(txn, m);
@@ -481,7 +483,7 @@ class IntroductionManagerImpl extends ConversationClientImpl
 			IntroduceeSession session = sessionParser
 					.parseIntroduceeSession(contactGroupId, bdfSession);
 			sessionId = session.getSessionId();
-			author = session.getRemoteAuthor();
+			author = session.getRemote().author;
 		} else throw new AssertionError();
 		return new IntroductionResponse(sessionId, m, contactGroupId,
 				role, meta.getTimestamp(), meta.isLocal(), status.isSent(),
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 43027b40b138a49f0ab5b2dea7c573be86c71ba3..6b59a64ae4fb954b485e126e846d2fc887c6018c 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
@@ -27,6 +27,7 @@ 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;
 import static org.briarproject.briar.introduction.MessageType.ACCEPT;
+import static org.briarproject.briar.introduction.MessageType.ACTIVATE;
 import static org.briarproject.briar.introduction.MessageType.AUTH;
 
 
@@ -55,8 +56,9 @@ class IntroductionValidator extends BdfMessageValidator {
 				return validateAcceptMessage(m, body);
 			case AUTH:
 				return validateAuthMessage(m, body);
-			case DECLINE:
 			case ACTIVATE:
+				return validateActivateMessage(m, body);
+			case DECLINE:
 			case ABORT:
 				return validateOtherMessage(type, m, body);
 			default:
@@ -149,6 +151,32 @@ class IntroductionValidator extends BdfMessageValidator {
 				Collections.singletonList(dependency));
 	}
 
+	private BdfMessageContext validateActivateMessage(Message m, BdfList body)
+			throws FormatException {
+		checkSize(body, 4);
+
+		byte[] sessionIdBytes = body.getRaw(1);
+		checkLength(sessionIdBytes, UniqueId.LENGTH);
+
+		byte[] previousMessageId = body.getOptionalRaw(2);
+		checkLength(previousMessageId, UniqueId.LENGTH);
+
+		byte[] mac = body.getOptionalRaw(3);
+		checkLength(mac, MAC_BYTES);
+
+		SessionId sessionId = new SessionId(sessionIdBytes);
+		BdfDictionary meta = messageEncoder
+				.encodeMetadata(ACTIVATE, sessionId, m.getTimestamp(), false,
+						false, false);
+		if (previousMessageId == null) {
+			return new BdfMessageContext(meta);
+		} else {
+			MessageId dependency = new MessageId(previousMessageId);
+			return new BdfMessageContext(meta,
+					Collections.singletonList(dependency));
+		}
+	}
+
 	private BdfMessageContext validateOtherMessage(MessageType type,
 			Message m, BdfList body) throws FormatException {
 		checkSize(body, 3);
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 42eb41c0f4cc439737fb1736b6356a247493ad35..2eed3057a0678723cded22c787391569a51e3ac8 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
@@ -47,7 +47,8 @@ interface MessageEncoder {
 			byte[] mac, byte[] signature);
 
 	Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId);
+			@Nullable MessageId previousMessageId, SessionId sessionId,
+			byte[] mac);
 
 	Message encodeAbortMessage(GroupId contactGroupId, long timestamp,
 			@Nullable MessageId previousMessageId, SessionId sessionId);
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 861569ac5ec9ffa7e5243a1c773b44abfa8c3d65..89d3e40e5912324bdc33a13debc54183336f41ed 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
@@ -100,12 +100,7 @@ class MessageEncoderImpl implements MessageEncoder {
 				clientHelper.toList(author),
 				message
 		);
-		try {
-			return messageFactory.createMessage(contactGroupId, timestamp,
-					clientHelper.toByteArray(body));
-		} catch (FormatException e) {
-			throw new AssertionError(e);
-		}
+		return createMessage(contactGroupId, timestamp, body);
 	}
 
 	@Override
@@ -119,14 +114,9 @@ class MessageEncoderImpl implements MessageEncoder {
 				previousMessageId,
 				ephemeralPublicKey,
 				acceptTimestamp,
-				encodeTransportProperties(transportProperties)
+				clientHelper.toDictionary(transportProperties)
 		);
-		try {
-			return messageFactory.createMessage(contactGroupId, timestamp,
-					clientHelper.toByteArray(body));
-		} catch (FormatException e) {
-			throw new AssertionError(e);
-		}
+		return createMessage(contactGroupId, timestamp, body);
 	}
 
 	@Override
@@ -147,19 +137,20 @@ class MessageEncoderImpl implements MessageEncoder {
 				mac,
 				signature
 		);
-		try {
-			return messageFactory.createMessage(contactGroupId, timestamp,
-					clientHelper.toByteArray(body));
-		} catch (FormatException e) {
-			throw new AssertionError(e);
-		}
+		return createMessage(contactGroupId, timestamp, body);
 	}
 
 	@Override
 	public Message encodeActivateMessage(GroupId contactGroupId, long timestamp,
-			@Nullable MessageId previousMessageId, SessionId sessionId) {
-		return encodeMessage(ACTIVATE, contactGroupId, sessionId, timestamp,
-				previousMessageId);
+			@Nullable MessageId previousMessageId, SessionId sessionId,
+			byte[] mac) {
+		BdfList body = BdfList.of(
+				ACTIVATE.getValue(),
+				sessionId,
+				previousMessageId,
+				mac
+		);
+		return createMessage(contactGroupId, timestamp, body);
 	}
 
 	@Override
@@ -177,6 +168,11 @@ class MessageEncoderImpl implements MessageEncoder {
 				sessionId,
 				previousMessageId
 		);
+		return createMessage(contactGroupId, timestamp, body);
+	}
+
+	private Message createMessage(GroupId contactGroupId, long timestamp,
+			BdfList body) {
 		try {
 			return messageFactory.createMessage(contactGroupId, timestamp,
 					clientHelper.toByteArray(body));
@@ -185,13 +181,4 @@ class MessageEncoderImpl implements MessageEncoder {
 		}
 	}
 
-	private BdfDictionary encodeTransportProperties(
-			Map<TransportId, TransportProperties> map) {
-		BdfDictionary d = new BdfDictionary();
-		for (Map.Entry<TransportId, TransportProperties> e : map.entrySet()) {
-			d.put(e.getKey().getString(), e.getValue());
-		}
-		return d;
-	}
-
 }
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java
index ef97282dd894e63069e65b08488fb37a5a7874d2..69ddd242d1cab3137d7e312905d16388362a629f 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/MessageParserImpl.java
@@ -124,8 +124,9 @@ class MessageParserImpl implements MessageParser {
 		SessionId sessionId = new SessionId(body.getRaw(1));
 		byte[] previousMsgBytes = body.getRaw(2);
 		MessageId previousMessageId = new MessageId(previousMsgBytes);
+		byte[] mac = body.getRaw(3);
 		return new ActivateMessage(m.getId(), m.getGroupId(), m.getTimestamp(),
-				previousMessageId, sessionId);
+				previousMessageId, sessionId, mac);
 	}
 
 	@Override
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java b/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java
index ddf04d044eb9e95522686d0c2113745a556ed91d..086dfb1a2975defaf177206dbce5cb65cac7edcb 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/Session.java
@@ -12,7 +12,7 @@ abstract class Session<S extends State> {
 
 	private final SessionId sessionId;
 	private final S state;
-	private long requestTimestamp;
+	private final long requestTimestamp;
 
 	Session(SessionId sessionId, S state, long requestTimestamp) {
 		this.sessionId = sessionId;
@@ -30,7 +30,7 @@ abstract class Session<S extends State> {
 		return state;
 	}
 
-	public long getRequestTimestamp() {
+	long getRequestTimestamp() {
 		return requestTimestamp;
 	}
 
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java
index 2ffee61d88f0743eb4f908c080ed93c00c4fd27f..9023f0d94d3ebb466b9229038766c7d903dd6bf4 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionEncoderImpl.java
@@ -7,6 +7,9 @@ import org.briarproject.bramble.api.identity.Author;
 import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
 import org.briarproject.bramble.api.plugin.TransportId;
 import org.briarproject.bramble.api.transport.KeySetId;
+import org.briarproject.briar.introduction.IntroduceeSession.Common;
+import org.briarproject.briar.introduction.IntroduceeSession.Local;
+import org.briarproject.briar.introduction.IntroduceeSession.Remote;
 import org.briarproject.briar.introduction.IntroducerSession.Introducee;
 
 import java.util.Map;
@@ -19,6 +22,7 @@ import static org.briarproject.bramble.api.data.BdfDictionary.NULL_VALUE;
 import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
 import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ALICE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
@@ -28,12 +32,12 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MAC_KEY;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
-import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
-import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
-import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
@@ -91,34 +95,43 @@ class SessionEncoderImpl implements SessionEncoder {
 	@Override
 	public BdfDictionary encodeIntroduceeSession(IntroduceeSession s) {
 		BdfDictionary d = encodeSession(s);
-		d.put(SESSION_KEY_LOCAL_TIMESTAMP, s.getLocalTimestamp());
-		putNullable(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID,
-				s.getLastLocalMessageId());
-		putNullable(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID,
-				s.getLastRemoteMessageId());
 		d.put(SESSION_KEY_INTRODUCER, clientHelper.toList(s.getIntroducer()));
-		d.put(SESSION_KEY_REMOTE_AUTHOR,
-				clientHelper.toList(s.getRemoteAuthor()));
-		putNullable(d, SESSION_KEY_EPHEMERAL_PUBLIC_KEY,
-				s.getEphemeralPublicKey());
-		putNullable(d, SESSION_KEY_EPHEMERAL_PRIVATE_KEY,
-				s.getEphemeralPrivateKey());
-		putNullable(d, SESSION_KEY_TRANSPORT_PROPERTIES,
-				s.getTransportProperties() == null ? null :
-						clientHelper.toDictionary(s.getTransportProperties()));
-		d.put(SESSION_KEY_ACCEPT_TIMESTAMP, s.getAcceptTimestamp());
+		d.put(SESSION_KEY_LOCAL, encodeLocal(s.getLocal()));
+		d.put(SESSION_KEY_REMOTE, encodeRemote(s.getRemote()));
 		putNullable(d, SESSION_KEY_MASTER_KEY, s.getMasterKey());
-		putNullable(d, SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY,
-				s.getRemotePublicKey());
-		putNullable(d, SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES,
-				s.getRemoteTransportProperties() == null ? null : clientHelper
-						.toDictionary(s.getRemoteTransportProperties()));
-		d.put(SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP, s.getRemoteAcceptTimestamp());
 		putNullable(d, SESSION_KEY_TRANSPORT_KEYS,
 				encodeTransportKeys(s.getTransportKeys()));
 		return d;
 	}
 
+	private BdfDictionary encodeCommon(Common s) {
+		BdfDictionary d = new BdfDictionary();
+		d.put(SESSION_KEY_ALICE, s.alice);
+		putNullable(d, SESSION_KEY_EPHEMERAL_PUBLIC_KEY, s.ephemeralPublicKey);
+		putNullable(d, SESSION_KEY_TRANSPORT_PROPERTIES,
+				s.transportProperties == null ? null :
+						clientHelper.toDictionary(s.transportProperties));
+		d.put(SESSION_KEY_ACCEPT_TIMESTAMP, s.acceptTimestamp);
+		putNullable(d, SESSION_KEY_MAC_KEY, s.macKey);
+		return d;
+	}
+
+	private BdfDictionary encodeLocal(Local s) {
+		BdfDictionary d = encodeCommon(s);
+		d.put(SESSION_KEY_LOCAL_TIMESTAMP, s.lastMessageTimestamp);
+		putNullable(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID, s.lastMessageId);
+		putNullable(d, SESSION_KEY_EPHEMERAL_PRIVATE_KEY,
+				s.ephemeralPrivateKey);
+		return d;
+	}
+
+	private BdfDictionary encodeRemote(Remote s) {
+		BdfDictionary d = encodeCommon(s);
+		d.put(SESSION_KEY_REMOTE_AUTHOR, clientHelper.toList(s.author));
+		putNullable(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID, s.lastMessageId);
+		return d;
+	}
+
 	private BdfDictionary encodeSession(Session s) {
 		BdfDictionary d = new BdfDictionary();
 		d.put(SESSION_KEY_SESSION_ID, s.getSessionId());
diff --git a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java
index 7c5b2192596a77b1e89d8ce1a52f7e94d6a70248..52c12e5477ae040cc77be6193eeed5108d600662 100644
--- a/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java
+++ b/briar-core/src/main/java/org/briarproject/briar/introduction/SessionParserImpl.java
@@ -13,6 +13,8 @@ import org.briarproject.bramble.api.sync.MessageId;
 import org.briarproject.bramble.api.transport.KeySetId;
 import org.briarproject.briar.api.client.SessionId;
 import org.briarproject.briar.api.introduction.Role;
+import org.briarproject.briar.introduction.IntroduceeSession.Local;
+import org.briarproject.briar.introduction.IntroduceeSession.Remote;
 import org.briarproject.briar.introduction.IntroducerSession.Introducee;
 
 import java.util.HashMap;
@@ -22,7 +24,10 @@ import javax.annotation.Nullable;
 import javax.annotation.concurrent.Immutable;
 import javax.inject.Inject;
 
+import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
+import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ACCEPT_TIMESTAMP;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ALICE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_AUTHOR;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PRIVATE_KEY;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_EPHEMERAL_PUBLIC_KEY;
@@ -32,20 +37,18 @@ import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_INTRODUCER;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_LOCAL_MESSAGE_ID;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LAST_REMOTE_MESSAGE_ID;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_LOCAL_TIMESTAMP;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MAC_KEY;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_MASTER_KEY;
-import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP;
+import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_AUTHOR;
-import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY;
-import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_REQUEST_TIMESTAMP;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_SESSION_ID;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_STATE;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_KEYS;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_TRANSPORT_PROPERTIES;
-import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
-import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
 
 @Immutable
 @NotNullByDefault
@@ -103,42 +106,55 @@ class SessionParserImpl implements SessionParser {
 		SessionId sessionId = getSessionId(d);
 		IntroduceeState state = IntroduceeState.fromValue(getState(d));
 		long requestTimestamp = d.getLong(SESSION_KEY_REQUEST_TIMESTAMP);
+		Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER);
+		Local local = parseLocal(d.getDictionary(SESSION_KEY_LOCAL));
+		Remote remote = parseRemote(d.getDictionary(SESSION_KEY_REMOTE));
+		byte[] masterKey = d.getOptionalRaw(SESSION_KEY_MASTER_KEY);
+		Map<TransportId, KeySetId> transportKeys = parseTransportKeys(
+				d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS));
+		return new IntroduceeSession(sessionId, state, requestTimestamp,
+				introducerGroupId, introducer, local, remote,
+				masterKey, transportKeys);
+	}
+
+	private Local parseLocal(BdfDictionary d) throws FormatException {
+		boolean alice = d.getBoolean(SESSION_KEY_ALICE);
 		MessageId lastLocalMessageId =
 				getMessageId(d, SESSION_KEY_LAST_LOCAL_MESSAGE_ID);
 		long localTimestamp = d.getLong(SESSION_KEY_LOCAL_TIMESTAMP);
-		MessageId lastRemoteMessageId =
-				getMessageId(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID);
-		Author introducer = getAuthor(d, SESSION_KEY_INTRODUCER);
 		byte[] ephemeralPublicKey =
 				d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PUBLIC_KEY);
+		BdfDictionary tpDict =
+				d.getOptionalDictionary(SESSION_KEY_TRANSPORT_PROPERTIES);
 		byte[] ephemeralPrivateKey =
 				d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PRIVATE_KEY);
+		Map<TransportId, TransportProperties> transportProperties =
+				tpDict == null ? null : clientHelper
+						.parseAndValidateTransportPropertiesMap(tpDict);
+		long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP);
+		byte[] macKey = d.getOptionalRaw(SESSION_KEY_MAC_KEY);
+		return new Local(alice, lastLocalMessageId, localTimestamp,
+				ephemeralPublicKey, ephemeralPrivateKey, transportProperties,
+				acceptTimestamp, macKey);
+	}
+
+	private Remote parseRemote(BdfDictionary d) throws FormatException {
+		boolean alice = d.getBoolean(SESSION_KEY_ALICE);
+		Author remoteAuthor = getAuthor(d, SESSION_KEY_REMOTE_AUTHOR);
+		MessageId lastRemoteMessageId =
+				getMessageId(d, SESSION_KEY_LAST_REMOTE_MESSAGE_ID);
+		byte[] ephemeralPublicKey =
+				d.getOptionalRaw(SESSION_KEY_EPHEMERAL_PUBLIC_KEY);
 		BdfDictionary tpDict =
 				d.getOptionalDictionary(SESSION_KEY_TRANSPORT_PROPERTIES);
 		Map<TransportId, TransportProperties> transportProperties =
 				tpDict == null ? null : clientHelper
 						.parseAndValidateTransportPropertiesMap(tpDict);
 		long acceptTimestamp = d.getLong(SESSION_KEY_ACCEPT_TIMESTAMP);
-		byte[] masterKey = d.getOptionalRaw(SESSION_KEY_MASTER_KEY);
-		Author remoteAuthor = getAuthor(d, SESSION_KEY_REMOTE_AUTHOR);
-		byte[] remoteEphemeralPublicKey =
-				d.getOptionalRaw(SESSION_KEY_REMOTE_EPHEMERAL_PUBLIC_KEY);
-		BdfDictionary rptDict = d.getOptionalDictionary(
-				SESSION_KEY_REMOTE_TRANSPORT_PROPERTIES);
-		Map<TransportId, TransportProperties> remoteTransportProperties =
-				rptDict == null ? null : clientHelper
-						.parseAndValidateTransportPropertiesMap(rptDict);
-		long remoteAcceptTimestamp =
-				d.getLong(SESSION_KEY_REMOTE_ACCEPT_TIMESTAMP);
-		Map<TransportId, KeySetId> transportKeys = parseTransportKeys(
-				d.getOptionalDictionary(SESSION_KEY_TRANSPORT_KEYS));
-		return new IntroduceeSession(sessionId, state, requestTimestamp,
-				introducerGroupId, lastLocalMessageId, localTimestamp,
-				lastRemoteMessageId, introducer, ephemeralPublicKey,
-				ephemeralPrivateKey, transportProperties, acceptTimestamp,
-				masterKey, remoteAuthor, remoteEphemeralPublicKey,
-				remoteTransportProperties, remoteAcceptTimestamp,
-				transportKeys);
+		byte[] macKey = d.getOptionalRaw(SESSION_KEY_MAC_KEY);
+		return new Remote(alice, remoteAuthor, lastRemoteMessageId,
+				ephemeralPublicKey, transportProperties, acceptTimestamp,
+				macKey);
 	}
 
 	private int getState(BdfDictionary d) throws FormatException {
diff --git a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java
index b8c636400d1ee102312f0fea368315d22728a426..59cf3b4f4ae9760312e0b7771753e9e3f8b445e0 100644
--- a/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java
+++ b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionCryptoImplTest.java
@@ -96,35 +96,35 @@ public class IntroductionCryptoImplTest extends BrambleTestCase {
 	}
 
 	@Test
-	public void testAliceMac() throws Exception {
+	public void testAliceAuthMac() throws Exception {
 		SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true);
 		byte[] aliceMac =
-				crypto.mac(aliceMacKey, introducer.getId(), alice.getId(),
+				crypto.authMac(aliceMacKey, introducer.getId(), alice.getId(),
 						bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp,
 						aliceEphemeral.getPublic().getEncoded(),
 						bobEphemeral.getPublic().getEncoded(), aliceTransport,
 						bobTransport, true);
 
-		crypto.verifyMac(aliceMac, masterKey, introducer.getId(), bob.getId(),
-				alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp,
-				bobEphemeral.getPublic().getEncoded(),
+		crypto.verifyAuthMac(aliceMac, aliceMacKey, introducer.getId(),
+				bob.getId(), alice.getId(), bobAcceptTimestamp,
+				aliceAcceptTimestamp, bobEphemeral.getPublic().getEncoded(),
 				aliceEphemeral.getPublic().getEncoded(), bobTransport,
 				aliceTransport, true);
 	}
 
 	@Test
-	public void testBobMac() throws Exception {
+	public void testBobAuthMac() throws Exception {
 		SecretKey bobMacKey = crypto.deriveMacKey(masterKey, false);
 		byte[] bobMac =
-				crypto.mac(bobMacKey, introducer.getId(), bob.getId(),
+				crypto.authMac(bobMacKey, introducer.getId(), bob.getId(),
 						alice.getId(), bobAcceptTimestamp, aliceAcceptTimestamp,
 						bobEphemeral.getPublic().getEncoded(),
 						aliceEphemeral.getPublic().getEncoded(), bobTransport,
 						aliceTransport, false);
 
-		crypto.verifyMac(bobMac, masterKey, introducer.getId(), alice.getId(),
-				bob.getId(), aliceAcceptTimestamp, bobAcceptTimestamp,
-				aliceEphemeral.getPublic().getEncoded(),
+		crypto.verifyAuthMac(bobMac, bobMacKey, introducer.getId(),
+				alice.getId(), bob.getId(), aliceAcceptTimestamp,
+				bobAcceptTimestamp, aliceEphemeral.getPublic().getEncoded(),
 				bobEphemeral.getPublic().getEncoded(), aliceTransport,
 				bobTransport, false);
 	}
@@ -139,4 +139,20 @@ public class IntroductionCryptoImplTest extends BrambleTestCase {
 				signature);
 	}
 
+	@Test
+	public void testAliceActivateMac() throws Exception {
+		SecretKey aliceMacKey = crypto.deriveMacKey(masterKey, true);
+		byte[] aliceMac = crypto.activateMac(aliceMacKey);
+
+		crypto.verifyActivateMac(aliceMac, aliceMacKey);
+	}
+
+	@Test
+	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/IntroductionIntegrationTest.java b/briar-core/src/test/java/org/briarproject/briar/introduction/IntroductionIntegrationTest.java
index 1adfb9207e6a76fc086ecb9424efff87ea1a7e1a..3dccbbc8dec17248f29b337b176263c7b6be2fd6 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
@@ -170,16 +170,6 @@ public class IntroductionIntegrationTest
 		sync1To0(1, true);
 		sync0To2(1, true);
 
-		// assert that introducee2 added introducee1
-		Contact contact1From2 = c2.getContactManager()
-				.getContact(author1.getId(), author2.getId());
-
-		// assert that introducee2 did add transport properties
-		// TODO check when notion of inactive contacts has been removed
-//		TransportProperties tp2 = c2.getTransportPropertyManager()
-//				.getRemoteProperties(contact1From2.getId(), TRANSPORT_ID);
-//		assertFalse(tp2.isEmpty());
-
 		// assert that introducee2 did add the transport keys
 		IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(),
 				introductionManager2.getContactGroup(contact0From2).getId());
@@ -194,7 +184,7 @@ public class IntroductionIntegrationTest
 		IntroduceeSession session1 = getIntroduceeSession(c1.getClientHelper(),
 				introductionManager1.getContactGroup(contact0From1).getId());
 		assertNull(session1.getMasterKey());
-		assertNull(session1.getEphemeralPrivateKey());
+		assertNull(session1.getLocal().ephemeralPrivateKey);
 		assertNull(session1.getTransportKeys());
 
 		// sync second ACTIVATE and its forward
@@ -533,16 +523,6 @@ public class IntroductionIntegrationTest
 		sync1To0(1, true);
 		sync0To2(1, true);
 
-		// assert that introducee2 did not add any transport properties
-		TransportProperties tp2 = c2.getTransportPropertyManager()
-				.getRemoteProperties(contactId1From2, TRANSPORT_ID);
-		assertTrue(tp2.isEmpty());
-
-		// assert that introducee2 did not add any transport keys
-		IntroduceeSession session2 = getIntroduceeSession(c2.getClientHelper(),
-				introductionManager2.getContactGroup(contact0From2).getId());
-		assertNull(session2.getTransportKeys());
-
 		// sync second AUTH and its forward as well as the following ACTIVATE
 		sync2To0(2, true);
 		sync0To1(2, true);
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 fbebeece406644f002922b5006550e942f28d38f..7614121cf54a19d6a5c23f553c6e9a163e923791 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
@@ -312,7 +312,7 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 	@Test
 	public void testAcceptsActivate() throws Exception {
 		BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
-				previousMsgId.getBytes());
+				previousMsgId.getBytes(), mac);
 
 		expectEncodeMetadata(ACTIVATE);
 		BdfMessageContext messageContext =
@@ -323,27 +323,37 @@ public class IntroductionValidatorTest extends ValidatorTestCase {
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooShortBodyForActivate() throws Exception {
-		BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes());
+		BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
+				previousMsgId.getBytes());
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsTooLongBodyForActivate() throws Exception {
 		BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
-				previousMsgId.getBytes(), null);
+				previousMsgId.getBytes(), mac, null);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInvalidSessionIdForActivate() throws Exception {
 		BdfList body =
-				BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes());
+				BdfList.of(ACTIVATE.getValue(), null, previousMsgId.getBytes(),
+						mac);
 		validator.validateMessage(message, group, body);
 	}
 
 	@Test(expected = FormatException.class)
 	public void testRejectsInvalidPreviousMsgIdForActivate() throws Exception {
-		BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1);
+		BdfList body =
+				BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(), 1, mac);
+		validator.validateMessage(message, group, body);
+	}
+
+	@Test(expected = FormatException.class)
+	public void testRejectsInvalidMacForActivate() throws Exception {
+		BdfList body = BdfList.of(ACTIVATE.getValue(), sessionId.getBytes(),
+				previousMsgId.getBytes(), getRandomBytes(MAC_BYTES - 1));
 		validator.validateMessage(message, group, body);
 	}
 
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 a4c155897960c43571e2933cbe95ed3584f7b843..f3480f15832cb6b3d80edb2281ee9dca09a986bc 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
@@ -165,17 +165,17 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
 						sessionId, ephemeralPublicKey, acceptTimestamp,
 						transportProperties);
 		validator.validateMessage(m, group, clientHelper.toList(m));
-		AcceptMessage rm =
+		AcceptMessage am =
 				messageParser.parseAcceptMessage(m, clientHelper.toList(m));
 
-		assertEquals(m.getId(), rm.getMessageId());
-		assertEquals(m.getGroupId(), rm.getGroupId());
-		assertEquals(m.getTimestamp(), rm.getTimestamp());
-		assertEquals(previousMsgId, rm.getPreviousMessageId());
-		assertEquals(sessionId, rm.getSessionId());
-		assertArrayEquals(ephemeralPublicKey, rm.getEphemeralPublicKey());
-		assertEquals(acceptTimestamp, rm.getAcceptTimestamp());
-		assertEquals(transportProperties, rm.getTransportProperties());
+		assertEquals(m.getId(), am.getMessageId());
+		assertEquals(m.getGroupId(), am.getGroupId());
+		assertEquals(m.getTimestamp(), am.getTimestamp());
+		assertEquals(previousMsgId, am.getPreviousMessageId());
+		assertEquals(sessionId, am.getSessionId());
+		assertArrayEquals(ephemeralPublicKey, am.getEphemeralPublicKey());
+		assertEquals(acceptTimestamp, am.getAcceptTimestamp());
+		assertEquals(transportProperties, am.getTransportProperties());
 	}
 
 	@Test
@@ -184,14 +184,14 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
 				.encodeDeclineMessage(groupId, timestamp, previousMsgId,
 						sessionId);
 		validator.validateMessage(m, group, clientHelper.toList(m));
-		DeclineMessage rm =
+		DeclineMessage dm =
 				messageParser.parseDeclineMessage(m, clientHelper.toList(m));
 
-		assertEquals(m.getId(), rm.getMessageId());
-		assertEquals(m.getGroupId(), rm.getGroupId());
-		assertEquals(m.getTimestamp(), rm.getTimestamp());
-		assertEquals(previousMsgId, rm.getPreviousMessageId());
-		assertEquals(sessionId, rm.getSessionId());
+		assertEquals(m.getId(), dm.getMessageId());
+		assertEquals(m.getGroupId(), dm.getGroupId());
+		assertEquals(m.getTimestamp(), dm.getTimestamp());
+		assertEquals(previousMsgId, dm.getPreviousMessageId());
+		assertEquals(sessionId, dm.getSessionId());
 	}
 
 	@Test
@@ -200,32 +200,33 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
 				.encodeAuthMessage(groupId, timestamp, previousMsgId,
 						sessionId, mac, signature);
 		validator.validateMessage(m, group, clientHelper.toList(m));
-		AuthMessage rm =
+		AuthMessage am =
 				messageParser.parseAuthMessage(m, clientHelper.toList(m));
 
-		assertEquals(m.getId(), rm.getMessageId());
-		assertEquals(m.getGroupId(), rm.getGroupId());
-		assertEquals(m.getTimestamp(), rm.getTimestamp());
-		assertEquals(previousMsgId, rm.getPreviousMessageId());
-		assertEquals(sessionId, rm.getSessionId());
-		assertArrayEquals(mac, rm.getMac());
-		assertArrayEquals(signature, rm.getSignature());
+		assertEquals(m.getId(), am.getMessageId());
+		assertEquals(m.getGroupId(), am.getGroupId());
+		assertEquals(m.getTimestamp(), am.getTimestamp());
+		assertEquals(previousMsgId, am.getPreviousMessageId());
+		assertEquals(sessionId, am.getSessionId());
+		assertArrayEquals(mac, am.getMac());
+		assertArrayEquals(signature, am.getSignature());
 	}
 
 	@Test
 	public void testActivateMessage() throws Exception {
 		Message m = messageEncoder
 				.encodeActivateMessage(groupId, timestamp, previousMsgId,
-						sessionId);
+						sessionId, mac);
 		validator.validateMessage(m, group, clientHelper.toList(m));
-		ActivateMessage rm =
+		ActivateMessage am =
 				messageParser.parseActivateMessage(m, clientHelper.toList(m));
 
-		assertEquals(m.getId(), rm.getMessageId());
-		assertEquals(m.getGroupId(), rm.getGroupId());
-		assertEquals(m.getTimestamp(), rm.getTimestamp());
-		assertEquals(previousMsgId, rm.getPreviousMessageId());
-		assertEquals(sessionId, rm.getSessionId());
+		assertEquals(m.getId(), am.getMessageId());
+		assertEquals(m.getGroupId(), am.getGroupId());
+		assertEquals(m.getTimestamp(), am.getTimestamp());
+		assertEquals(previousMsgId, am.getPreviousMessageId());
+		assertEquals(sessionId, am.getSessionId());
+		assertArrayEquals(mac, am.getMac());
 	}
 
 	@Test
@@ -234,14 +235,14 @@ public class MessageEncoderParserIntegrationTest extends BrambleTestCase {
 				.encodeAbortMessage(groupId, timestamp, previousMsgId,
 						sessionId);
 		validator.validateMessage(m, group, clientHelper.toList(m));
-		AbortMessage rm =
+		AbortMessage am =
 				messageParser.parseAbortMessage(m, clientHelper.toList(m));
 
-		assertEquals(m.getId(), rm.getMessageId());
-		assertEquals(m.getGroupId(), rm.getGroupId());
-		assertEquals(m.getTimestamp(), rm.getTimestamp());
-		assertEquals(previousMsgId, rm.getPreviousMessageId());
-		assertEquals(sessionId, rm.getSessionId());
+		assertEquals(m.getId(), am.getMessageId());
+		assertEquals(m.getGroupId(), am.getGroupId());
+		assertEquals(m.getTimestamp(), am.getTimestamp());
+		assertEquals(previousMsgId, am.getPreviousMessageId());
+		assertEquals(sessionId, am.getSessionId());
 	}
 
 }
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 9d773bac0c4fbd0bcd6a2e354a2fd562b300b52c..ffbcaa94369cd587837143c338c9198e7dfdbcbe 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
@@ -28,15 +28,19 @@ import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
 import static org.briarproject.bramble.test.TestUtils.getRandomId;
 import static org.briarproject.bramble.test.TestUtils.getTransportId;
 import static org.briarproject.bramble.test.TestUtils.getTransportPropertiesMap;
-import static org.briarproject.bramble.util.StringUtils.getRandomString;
+import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
+import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
+import static org.briarproject.briar.introduction.IntroduceeSession.Local;
+import static org.briarproject.briar.introduction.IntroduceeSession.Remote;
 import static org.briarproject.briar.introduction.IntroduceeState.LOCAL_ACCEPTED;
 import static org.briarproject.briar.introduction.IntroducerState.AWAIT_AUTHS;
 import static org.briarproject.briar.introduction.IntroductionConstants.SESSION_KEY_ROLE;
-import static org.briarproject.briar.api.introduction.Role.INTRODUCEE;
-import static org.briarproject.briar.api.introduction.Role.INTRODUCER;
+import static org.briarproject.briar.test.BriarTestUtils.getRealAuthor;
 import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 
 public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 
@@ -74,6 +78,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 	private final Map<TransportId, TransportProperties>
 			remoteTransportProperties = getTransportPropertiesMap(3);
 	private final Map<TransportId, KeySetId> transportKeys = new HashMap<>();
+	private final byte[] localMacKey = getRandomBytes(SecretKey.LENGTH);
+	private final byte[] remoteMacKey = getRandomBytes(SecretKey.LENGTH);
 
 	public SessionEncoderParserIntegrationTest() {
 		BriarIntegrationTestComponent component =
@@ -82,8 +88,8 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 
 		sessionEncoder = new SessionEncoderImpl(clientHelper);
 		sessionParser = new SessionParserImpl(clientHelper);
-		author1 = getRealAuthor();
-		author2 = getRealAuthor();
+		author1 = getRealAuthor(authorFactory);
+		author2 = getRealAuthor(authorFactory);
 		transportKeys.put(getTransportId(), new KeySetId(1));
 		transportKeys.put(getTransportId(), new KeySetId(2));
 		transportKeys.put(getTransportId(), new KeySetId(3));
@@ -167,48 +173,70 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 		assertEquals(s1.getSessionId(), s2.getSessionId());
 		assertEquals(groupId1, s1.getContactGroupId());
 		assertEquals(s1.getContactGroupId(), s2.getContactGroupId());
+		assertEquals(author1, s1.getIntroducer());
+		assertEquals(s1.getIntroducer(), s2.getIntroducer());
+		assertArrayEquals(masterKey, s1.getMasterKey());
+		assertArrayEquals(s1.getMasterKey(), s2.getMasterKey());
+		assertEquals(transportKeys, s1.getTransportKeys());
+		assertEquals(s1.getTransportKeys(), s2.getTransportKeys());
 		assertEquals(localTimestamp, s1.getLocalTimestamp());
 		assertEquals(s1.getLocalTimestamp(), s2.getLocalTimestamp());
 		assertEquals(lastLocalMessageId, s1.getLastLocalMessageId());
 		assertEquals(s1.getLastLocalMessageId(), s2.getLastLocalMessageId());
 		assertEquals(lastRemoteMessageId, s1.getLastRemoteMessageId());
 		assertEquals(s1.getLastRemoteMessageId(), s2.getLastRemoteMessageId());
-		assertEquals(author1, s1.getIntroducer());
-		assertEquals(s1.getIntroducer(), s2.getIntroducer());
-		assertEquals(author2, s1.getRemoteAuthor());
-		assertEquals(s1.getRemoteAuthor(), s2.getRemoteAuthor());
-		assertArrayEquals(ephemeralPublicKey, s1.getEphemeralPublicKey());
-		assertArrayEquals(s1.getEphemeralPublicKey(),
-				s2.getEphemeralPublicKey());
-		assertArrayEquals(ephemeralPrivateKey, s1.getEphemeralPrivateKey());
-		assertArrayEquals(s1.getEphemeralPrivateKey(),
-				s2.getEphemeralPrivateKey());
-		assertEquals(acceptTimestamp, s1.getAcceptTimestamp());
-		assertEquals(s1.getAcceptTimestamp(), s2.getAcceptTimestamp());
-		assertArrayEquals(masterKey, s1.getMasterKey());
-		assertArrayEquals(s1.getMasterKey(), s2.getMasterKey());
-		assertArrayEquals(remoteEphemeralPublicKey, s1.getRemotePublicKey());
-		assertArrayEquals(s1.getRemotePublicKey(),
-				s2.getRemotePublicKey());
-		assertEquals(transportProperties, s1.getTransportProperties());
-		assertEquals(s1.getTransportProperties(), s2.getTransportProperties());
+
+		// check local
+		assertTrue(s1.getLocal().alice);
+		assertEquals(s1.getLocal().alice, s2.getLocal().alice);
+		assertEquals(lastLocalMessageId, s1.getLocal().lastMessageId);
+		assertEquals(s1.getLocal().lastMessageId, s2.getLocal().lastMessageId);
+		assertEquals(localTimestamp, s1.getLocal().lastMessageTimestamp);
+		assertEquals(s1.getLocal().lastMessageTimestamp,
+				s2.getLocal().lastMessageTimestamp);
+		assertArrayEquals(ephemeralPublicKey, s1.getLocal().ephemeralPublicKey);
+		assertArrayEquals(s1.getLocal().ephemeralPublicKey,
+				s2.getLocal().ephemeralPublicKey);
+		assertArrayEquals(ephemeralPrivateKey,
+				s1.getLocal().ephemeralPrivateKey);
+		assertArrayEquals(s1.getLocal().ephemeralPrivateKey,
+				s2.getLocal().ephemeralPrivateKey);
+		assertEquals(transportProperties, s1.getLocal().transportProperties);
+		assertEquals(s1.getLocal().transportProperties,
+				s2.getLocal().transportProperties);
+		assertEquals(acceptTimestamp, s1.getLocal().acceptTimestamp);
+		assertEquals(s1.getLocal().acceptTimestamp,
+				s2.getLocal().acceptTimestamp);
+		assertArrayEquals(localMacKey, s1.getLocal().macKey);
+		assertArrayEquals(s1.getLocal().macKey, s2.getLocal().macKey);
+
+		// check remote
+		assertFalse(s1.getRemote().alice);
+		assertEquals(s1.getRemote().alice, s2.getRemote().alice);
+		assertEquals(author2, s1.getRemote().author);
+		assertEquals(s1.getRemote().author, s2.getRemote().author);
+		assertEquals(lastRemoteMessageId, s1.getRemote().lastMessageId);
+		assertEquals(s1.getRemote().lastMessageId,
+				s2.getRemote().lastMessageId);
+		assertArrayEquals(remoteEphemeralPublicKey,
+				s1.getRemote().ephemeralPublicKey);
+		assertArrayEquals(s1.getRemote().ephemeralPublicKey,
+				s2.getRemote().ephemeralPublicKey);
 		assertEquals(remoteTransportProperties,
-				s1.getRemoteTransportProperties());
-		assertEquals(s1.getRemoteTransportProperties(),
-				s2.getRemoteTransportProperties());
-		assertEquals(remoteAcceptTimestamp, s1.getRemoteAcceptTimestamp());
-		assertEquals(s1.getRemoteAcceptTimestamp(), s2.getRemoteAcceptTimestamp());
-		assertEquals(transportKeys, s1.getTransportKeys());
-		assertEquals(s1.getTransportKeys(), s2.getTransportKeys());
+				s1.getRemote().transportProperties);
+		assertEquals(s1.getRemote().transportProperties,
+				s2.getRemote().transportProperties);
+		assertEquals(remoteAcceptTimestamp, s1.getRemote().acceptTimestamp);
+		assertEquals(s1.getRemote().acceptTimestamp,
+				s2.getRemote().acceptTimestamp);
+		assertArrayEquals(remoteMacKey, s1.getRemote().macKey);
+		assertArrayEquals(s1.getRemote().macKey, s2.getRemote().macKey);
 	}
 
 	@Test
 	public void testIntroduceeSessionWithNulls() throws FormatException {
-		IntroduceeSession s1 =
-				new IntroduceeSession(sessionId, LOCAL_ACCEPTED,
-						requestTimestamp, groupId1, null, localTimestamp, null,
-						author1, null, null, null, acceptTimestamp, null,
-						author2, null, null, remoteAcceptTimestamp, null);
+		IntroduceeSession s1 = IntroduceeSession
+				.getInitial(groupId1, sessionId, author1, false, author2);
 
 		BdfDictionary d = sessionEncoder.encodeIntroduceeSession(s1);
 		IntroduceeSession s2 =
@@ -218,14 +246,38 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 		assertEquals(s1.getLastLocalMessageId(), s2.getLastLocalMessageId());
 		assertNull(s1.getLastRemoteMessageId());
 		assertEquals(s1.getLastRemoteMessageId(), s2.getLastRemoteMessageId());
-		assertNull(s1.getEphemeralPublicKey());
-		assertArrayEquals(s1.getEphemeralPublicKey(),
-				s2.getEphemeralPublicKey());
-		assertNull(s1.getEphemeralPrivateKey());
-		assertArrayEquals(s1.getEphemeralPrivateKey(),
-				s2.getEphemeralPrivateKey());
+		assertNull(s1.getMasterKey());
+		assertEquals(s1.getMasterKey(), s2.getMasterKey());
 		assertNull(s1.getTransportKeys());
 		assertEquals(s1.getTransportKeys(), s2.getTransportKeys());
+
+		// check local
+		assertNull(s1.getLocal().lastMessageId);
+		assertEquals(s1.getLocal().lastMessageId, s2.getLocal().lastMessageId);
+		assertNull(s1.getLocal().ephemeralPublicKey);
+		assertEquals(s1.getLocal().ephemeralPublicKey,
+				s2.getLocal().ephemeralPublicKey);
+		assertNull(s1.getLocal().ephemeralPrivateKey);
+		assertEquals(s1.getLocal().ephemeralPrivateKey,
+				s2.getLocal().ephemeralPrivateKey);
+		assertNull(s1.getLocal().transportProperties);
+		assertEquals(s1.getLocal().transportProperties,
+				s2.getLocal().transportProperties);
+		assertNull(s1.getLocal().macKey);
+		assertEquals(s1.getLocal().macKey, s2.getLocal().macKey);
+
+		// check remote
+		assertNull(s1.getRemote().lastMessageId);
+		assertEquals(s1.getRemote().lastMessageId,
+				s2.getRemote().lastMessageId);
+		assertNull(s1.getRemote().ephemeralPublicKey);
+		assertEquals(s1.getRemote().ephemeralPublicKey,
+				s2.getRemote().ephemeralPublicKey);
+		assertNull(s1.getRemote().transportProperties);
+		assertEquals(s1.getRemote().transportProperties,
+				s2.getRemote().transportProperties);
+		assertNull(s1.getRemote().macKey);
+		assertEquals(s1.getRemote().macKey, s2.getRemote().macKey);
 	}
 
 	@Test(expected = FormatException.class)
@@ -256,13 +308,15 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 	}
 
 	private IntroduceeSession getIntroduceeSession() {
+		Local local = new Local(true, lastLocalMessageId, localTimestamp,
+				ephemeralPublicKey, ephemeralPrivateKey, transportProperties,
+				acceptTimestamp, localMacKey);
+		Remote remote = new Remote(false, author2, lastRemoteMessageId,
+				remoteEphemeralPublicKey,  remoteTransportProperties,
+				remoteAcceptTimestamp, remoteMacKey);
 		return new IntroduceeSession(sessionId, LOCAL_ACCEPTED,
-				requestTimestamp, groupId1, lastLocalMessageId, localTimestamp,
-				lastRemoteMessageId, author1, ephemeralPublicKey,
-				ephemeralPrivateKey, transportProperties, acceptTimestamp,
-				masterKey, author2, remoteEphemeralPublicKey,
-				remoteTransportProperties, remoteAcceptTimestamp,
-				transportKeys);
+				requestTimestamp, groupId1, author1, local, remote,
+				masterKey, transportKeys);
 	}
 
 	private void assertIntroduceeEquals(Introducee i1, Introducee i2) {
@@ -273,9 +327,4 @@ public class SessionEncoderParserIntegrationTest extends BrambleTestCase {
 		assertEquals(i1.lastRemoteMessageId, i2.lastRemoteMessageId);
 	}
 
-	private Author getRealAuthor() {
-		return authorFactory.createAuthor(getRandomString(5),
-				getRandomBytes(MAX_PUBLIC_KEY_LENGTH));
-	}
-
 }