diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java
index 9d74b6d2a0fe91e94382b034721f8ff38f89e2ec..0de25f31f9f4542b8c9a5c2a9278ff804b38ef17 100644
--- a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java
+++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTest.java
@@ -2,10 +2,13 @@ package org.briarproject;
 
 import net.jodah.concurrentunit.Waiter;
 
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
+import org.briarproject.api.crypto.CryptoComponent;
+import org.briarproject.api.crypto.KeyPair;
 import org.briarproject.api.crypto.SecretKey;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfEntry;
@@ -23,6 +26,7 @@ import org.briarproject.api.event.MessageStateChangedEvent;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.identity.LocalAuthor;
+import org.briarproject.api.introduction.IntroducerProtocolState;
 import org.briarproject.api.introduction.IntroductionManager;
 import org.briarproject.api.introduction.IntroductionMessage;
 import org.briarproject.api.introduction.IntroductionRequest;
@@ -34,7 +38,6 @@ import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.MessageId;
 import org.briarproject.api.sync.SyncSession;
 import org.briarproject.api.sync.SyncSessionFactory;
-import org.briarproject.api.sync.ValidationManager;
 import org.briarproject.api.sync.ValidationManager.State;
 import org.briarproject.api.system.Clock;
 import org.briarproject.contact.ContactModule;
@@ -67,12 +70,23 @@ import javax.inject.Inject;
 import static org.briarproject.TestPluginsModule.MAX_LATENCY;
 import static org.briarproject.TestPluginsModule.TRANSPORT_ID;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
+import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
+import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
+import static org.briarproject.api.introduction.IntroductionConstants.STATE;
+import static org.briarproject.api.introduction.IntroductionConstants.TIME;
+import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
+import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
+import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
 import static org.briarproject.api.sync.ValidationManager.State.DELIVERED;
 import static org.briarproject.api.sync.ValidationManager.State.INVALID;
 import static org.junit.Assert.assertEquals;
@@ -81,16 +95,18 @@ import static org.junit.Assert.assertTrue;
 
 public class IntroductionIntegrationTest extends BriarTestCase {
 
-	LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
-	SyncSessionFactory sync0, sync1, sync2;
-	ContactManager contactManager0, contactManager1, contactManager2;
-	ContactId contactId0, contactId1, contactId2;
-	IdentityManager identityManager0, identityManager1, identityManager2;
-	LocalAuthor author0, author1, author2;
+	private LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
+	private SyncSessionFactory sync0, sync1, sync2;
+	private ContactManager contactManager0, contactManager1, contactManager2;
+	private ContactId contactId0, contactId1, contactId2;
+	private IdentityManager identityManager0, identityManager1, identityManager2;
+	private LocalAuthor author0, author1, author2;
 
 	@Inject
 	Clock clock;
 	@Inject
+	CryptoComponent crypto;
+	@Inject
 	AuthorFactory authorFactory;
 
 	// objects accessed from background threads need to be volatile
@@ -155,32 +171,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroductionSession() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			contactId2 = contactManager0.addContact(author2,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0,
-					author1.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -260,29 +257,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroductionSessionFirstDecline() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			contactId2 = contactManager0.addContact(author2, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0, author1.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -353,29 +334,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroductionSessionSecondDecline() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			contactId2 = contactManager0.addContact(author2, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0, author1.getId(),
-					master, clock.currentTimeMillis(), false, true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), false,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -441,29 +406,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroductionSessionDelayedFirstDecline() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			contactId2 = contactManager0.addContact(author2, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0, author1.getId(),
-					master, clock.currentTimeMillis(), false, true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), false,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -520,21 +469,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroductionToSameContact() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducee as contact
-			contactId1 = contactManager0.addContact(author1, author0.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0, author1.getId(),
-					master, clock.currentTimeMillis(), true, true, true
-			);
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -670,32 +611,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testSessionIdReuse() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			contactId2 = contactManager0.addContact(author2,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0,
-					author1.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -767,32 +689,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroducerRemovedCleanup() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			contactId2 = contactManager0.addContact(author2,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0,
-					author1.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -853,32 +756,13 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	public void testIntroduceesRemovedCleanup() throws Exception {
 		startLifecycles();
 		try {
-			// Add Identities
+			// Add Identities And Contacts
 			addDefaultIdentities();
+			addDefaultContacts();
 
 			// Add Transport Properties
 			addTransportProperties();
 
-			// Add introducees as contacts
-			contactId1 = contactManager0.addContact(author1,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			contactId2 = contactManager0.addContact(author2,
-					author0.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			// Add introducer back
-			contactId0 = contactManager1.addContact(author0,
-					author1.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			ContactId contactId02 = contactManager2.addContact(author0,
-					author2.getId(), master, clock.currentTimeMillis(), true,
-					true, true
-			);
-			assertTrue(contactId0.equals(contactId02));
-
 			// listen to events
 			IntroducerListener listener0 = new IntroducerListener();
 			t0.getEventBus().addListener(listener0);
@@ -949,7 +833,149 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 		}
 	}
 
-	// TODO add a test for faking responses when #256 is implemented
+	@Test
+	public void testFakeResponse() throws Exception {
+		startLifecycles();
+		try {
+			addDefaultIdentities();
+			addDefaultContacts();
+			addTransportProperties();
+
+			// listen to events
+			IntroducerListener listener0 = new IntroducerListener();
+			t0.getEventBus().addListener(listener0);
+			IntroduceeListener listener1 = new IntroduceeListener(1, true);
+			t1.getEventBus().addListener(listener1);
+			IntroduceeListener listener2 = new IntroduceeListener(2, true);
+			t2.getEventBus().addListener(listener2);
+
+			// make introduction
+			long time = clock.currentTimeMillis();
+			Contact introducee1 = contactManager0.getContact(contactId1);
+			Contact introducee2 = contactManager0.getContact(contactId2);
+			introductionManager0
+					.makeIntroduction(introducee1, introducee2, "Hi!", time);
+
+			// sync first request message
+			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
+			eventWaiter.await(TIMEOUT, 1);
+			assertTrue(listener1.requestReceived);
+
+			// sync first response
+			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
+			eventWaiter.await(TIMEOUT, 1);
+			assertTrue(listener0.response1Received);
+
+			// get SessionId
+			List<IntroductionMessage> list = new ArrayList<>(
+					introductionManager1.getIntroductionMessages(contactId0));
+			assertEquals(2, list.size());
+			assertTrue(list.get(0) instanceof IntroductionRequest);
+			IntroductionRequest msg = (IntroductionRequest) list.get(0);
+			SessionId sessionId = msg.getSessionId();
+
+			// get contact group
+			IntroductionGroupFactory groupFactory =
+					t0.getIntroductionGroupFactory();
+			Group group = groupFactory.createIntroductionGroup(introducee1);
+
+			// get data for contact2
+			long timestamp = clock.currentTimeMillis();
+			KeyPair eKeyPair = crypto.generateAgreementKeyPair();
+			byte[] ePublicKey = eKeyPair.getPublic().getEncoded();
+			TransportProperties tp = new TransportProperties(
+					Collections.singletonMap("key", "value"));
+			BdfDictionary tpDict = BdfDictionary.of(new BdfEntry("fake", tp));
+
+			// create a fake response
+			BdfDictionary d = BdfDictionary.of(
+					new BdfEntry(TYPE, TYPE_RESPONSE),
+					new BdfEntry(SESSION_ID, sessionId),
+					new BdfEntry(GROUP_ID, group.getId()),
+					new BdfEntry(ACCEPT, true),
+					new BdfEntry(TIME, timestamp),
+					new BdfEntry(E_PUBLIC_KEY, ePublicKey),
+					new BdfEntry(TRANSPORT, tpDict)
+			);
+
+			// add the message to the queue
+			DatabaseComponent db0 = t0.getDatabaseComponent();
+			MessageSender sender0 = t0.getMessageSender();
+			Transaction txn = db0.startTransaction(false);
+			try {
+				sender0.sendMessage(txn, d);
+				txn.setComplete();
+			} finally {
+				db0.endTransaction(txn);
+			}
+
+			// send the fake response
+			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
+
+			// fake session state for introducer, so she doesn't abort
+			ClientHelper clientHelper0 = t0.getClientHelper();
+			BdfDictionary state =
+					clientHelper0.getMessageMetadataAsDictionary(sessionId);
+			state.put(STATE, IntroducerProtocolState.AWAIT_ACKS.getValue());
+			clientHelper0.mergeMessageMetadata(sessionId, state);
+
+			// sync back the ACK
+			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
+
+			// create a fake ACK
+			// TODO do we need to actually calculate a MAC and signature here?
+			byte[] mac = TestUtils.getRandomBytes(MAC_LENGTH);
+			byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
+			d = BdfDictionary.of(
+					new BdfEntry(TYPE, TYPE_ACK),
+					new BdfEntry(SESSION_ID, sessionId),
+					new BdfEntry(GROUP_ID, group.getId()),
+					new BdfEntry(MAC, mac),
+					new BdfEntry(SIGNATURE, sig)
+			);
+
+			// add the fake ACK to the message queue
+			txn = db0.startTransaction(false);
+			try {
+				sender0.sendMessage(txn, d);
+				txn.setComplete();
+			} finally {
+				db0.endTransaction(txn);
+			}
+
+			// make sure the contact was already added (as inactive)
+			DatabaseComponent db1 = t1.getDatabaseComponent();
+			txn = db1.startTransaction(true);
+			try {
+				assertEquals(2, db1.getContacts(txn).size());
+				txn.setComplete();
+			} finally {
+				db1.endTransaction(txn);
+			}
+
+			// send the fake ACK
+			deliverMessage(sync0, contactId0, sync1, contactId1, "0 to 1");
+
+			// make sure session was aborted and contact deleted again
+			txn = db1.startTransaction(true);
+			try {
+				assertEquals(1, db1.getContacts(txn).size());
+				txn.setComplete();
+			} finally {
+				db1.endTransaction(txn);
+			}
+
+			// there should now be an abort message to sync back
+			deliverMessage(sync1, contactId1, sync0, contactId0, "1 to 0");
+
+			// ensure introducer got the abort
+			state = clientHelper0.getMessageMetadataAsDictionary(sessionId);
+			assertEquals(IntroducerProtocolState.ERROR.getValue(),
+					state.getLong(STATE).intValue());
+		} finally {
+			stopLifecycles();
+		}
+	}
 
 	@After
 	public void tearDown() throws InterruptedException {
@@ -992,20 +1018,48 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 	}
 
 	private void addDefaultIdentities() throws DbException {
-		author0 = authorFactory.createLocalAuthor(INTRODUCER,
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
-				TestUtils.getRandomBytes(123));
+		KeyPair keyPair0 = crypto.generateSignatureKeyPair();
+		byte[] publicKey0 = keyPair0.getPublic().getEncoded();
+		byte[] privateKey0 = keyPair0.getPrivate().getEncoded();
+		author0 = authorFactory
+				.createLocalAuthor(INTRODUCER, publicKey0, privateKey0);
 		identityManager0.addLocalAuthor(author0);
-		author1 = authorFactory.createLocalAuthor(INTRODUCEE1,
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
-				TestUtils.getRandomBytes(123));
+		KeyPair keyPair1 = crypto.generateSignatureKeyPair();
+		byte[] publicKey1 = keyPair1.getPublic().getEncoded();
+		byte[] privateKey1 = keyPair1.getPrivate().getEncoded();
+		author1 = authorFactory
+				.createLocalAuthor(INTRODUCEE1, publicKey1, privateKey1);
 		identityManager1.addLocalAuthor(author1);
-		author2 = authorFactory.createLocalAuthor(INTRODUCEE2,
-				TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
-				TestUtils.getRandomBytes(123));
+		KeyPair keyPair2 = crypto.generateSignatureKeyPair();
+		byte[] publicKey2 = keyPair2.getPublic().getEncoded();
+		byte[] privateKey2 = keyPair2.getPrivate().getEncoded();
+		author2 = authorFactory
+				.createLocalAuthor(INTRODUCEE2, publicKey2, privateKey2);
 		identityManager2.addLocalAuthor(author2);
 	}
 
+	private void addDefaultContacts() throws DbException {
+		// Add introducees as contacts
+		contactId1 = contactManager0.addContact(author1,
+				author0.getId(), master, clock.currentTimeMillis(), true,
+				true, true
+		);
+		contactId2 = contactManager0.addContact(author2,
+				author0.getId(), master, clock.currentTimeMillis(), true,
+				true, true
+		);
+		// Add introducer back
+		contactId0 = contactManager1.addContact(author0,
+				author1.getId(), master, clock.currentTimeMillis(), true,
+				true, true
+		);
+		ContactId contactId02 = contactManager2.addContact(author0,
+				author2.getId(), master, clock.currentTimeMillis(), true,
+				true, true
+		);
+		assertTrue(contactId0.equals(contactId02));
+	}
+
 	private void deliverMessage(SyncSessionFactory fromSync, ContactId fromId,
 			SyncSessionFactory toSync, ContactId toId)
 			throws IOException, TimeoutException {
@@ -1050,14 +1104,14 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 
 	private class IntroduceeListener implements EventListener {
 
-		public volatile boolean requestReceived = false;
-		public volatile boolean succeeded = false;
-		public volatile boolean aborted = false;
+		private volatile boolean requestReceived = false;
+		private volatile boolean succeeded = false;
+		private volatile boolean aborted = false;
 
 		private final int introducee;
 		private final boolean accept;
 
-		IntroduceeListener(int introducee, boolean accept) {
+		private IntroduceeListener(int introducee, boolean accept) {
 			this.introducee = introducee;
 			this.accept = accept;
 		}
@@ -1126,9 +1180,9 @@ public class IntroductionIntegrationTest extends BriarTestCase {
 
 	private class IntroducerListener implements EventListener {
 
-		public volatile boolean response1Received = false;
-		public volatile boolean response2Received = false;
-		public volatile boolean aborted = false;
+		private volatile boolean response1Received = false;
+		private volatile boolean response2Received = false;
+		private volatile boolean aborted = false;
 
 		@Override
 		public void eventOccurred(Event e) {
diff --git a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java
index 733082ef0bd446a8c9ab805e6a11091de9653833..8e3e770420efdd5bdf6c6133f25926b55cd971c4 100644
--- a/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java
+++ b/briar-android-tests/src/test/java/org/briarproject/IntroductionIntegrationTestComponent.java
@@ -1,5 +1,6 @@
 package org.briarproject;
 
+import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.contact.ContactManager;
 import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.event.EventBus;
@@ -85,6 +86,8 @@ public interface IntroductionIntegrationTestComponent {
 
 	DatabaseComponent getDatabaseComponent();
 
+	ClientHelper getClientHelper();
+
 	MessageSender getMessageSender();
 
 	IntroductionGroupFactory getIntroductionGroupFactory();
diff --git a/briar-api/src/org/briarproject/api/contact/ContactManager.java b/briar-api/src/org/briarproject/api/contact/ContactManager.java
index 409d26d88f5de1cc6c1efb76e474e052093a88ba..5203da3004764c7d36909ca354a559d8e2905b7c 100644
--- a/briar-api/src/org/briarproject/api/contact/ContactManager.java
+++ b/briar-api/src/org/briarproject/api/contact/ContactManager.java
@@ -41,8 +41,12 @@ public interface ContactManager {
 	/** Removes a contact and all associated state. */
 	void removeContact(ContactId c) throws DbException;
 
+	/** Removes a contact and all associated state. */
+	void removeContact(Transaction txn, ContactId c) throws DbException;
+
 	/** Marks a contact as active or inactive. */
-	void setContactActive(ContactId c, boolean active) throws DbException;
+	void setContactActive(Transaction txn, ContactId c, boolean active)
+			throws DbException;
 
 	/** Return true if a contact with this name and public key already exists */
 	boolean contactExists(Transaction txn, AuthorId remoteAuthorId,
diff --git a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java
index 1806f76d72b91dc9fda9d0ea71c9b72fe8d68995..50a9c94d565d612f645fc5b5f8cbba561d937f18 100644
--- a/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java
+++ b/briar-api/src/org/briarproject/api/crypto/CryptoComponent.java
@@ -43,6 +43,12 @@ public interface CryptoComponent {
 	 */
 	SecretKey deriveHeaderKey(SecretKey master, boolean alice);
 
+	/**
+	 * Derives a message authentication code key from the given master secret.
+	 * @param alice whether the key is for use by Alice or Bob.
+	 */
+	SecretKey deriveMacKey(SecretKey master, boolean alice);
+
 	/**
 	 * Derives a nonce from the given master secret for one of the parties to
 	 * sign.
diff --git a/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java b/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java
index db50467f08b2fe29188f2c3b2352ab516bfd3652..324ba5bb613d7cb902af9aed0bff813c71669370 100644
--- a/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java
+++ b/briar-api/src/org/briarproject/api/introduction/IntroduceeAction.java
@@ -32,6 +32,7 @@ public enum IntroduceeAction {
 	public static IntroduceeAction getLocal(int type, boolean accept) {
 		if (type == TYPE_RESPONSE && accept) return LOCAL_ACCEPT;
 		if (type == TYPE_RESPONSE) return LOCAL_DECLINE;
+		if (type == TYPE_ACK) return ACK;
 		if (type == TYPE_ABORT) return LOCAL_ABORT;
 		return null;
 	}
diff --git a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
index c3376e36eedf76e38a7551ea5574ce993f117f5a..ac8975e5c2f7d83950dfcbf45a89353a5d615f1b 100644
--- a/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
+++ b/briar-api/src/org/briarproject/api/introduction/IntroductionConstants.java
@@ -26,6 +26,11 @@ public interface IntroductionConstants {
 	String TRANSPORT = "transport";
 	String MESSAGE_ID = "messageId";
 	String MESSAGE_TIME = "timestamp";
+	String MAC = "mac";
+	String SIGNATURE = "signature";
+
+	/* Validation Constants */
+	int MAC_LENGTH = 32;
 
 	/* Introducer Local State Metadata */
 	String STATE = "state";
@@ -59,6 +64,11 @@ public interface IntroductionConstants {
 	String EXISTS = "contactExists";
 	String REMOTE_AUTHOR_IS_US = "remoteAuthorIsUs";
 	String ANSWERED = "answered";
+	String NONCE = "nonce";
+	String MAC_KEY = "macKey";
+	String OUR_TRANSPORT = "ourTransport";
+	String OUR_MAC = "ourMac";
+	String OUR_SIGNATURE = "ourSignature";
 
 	String TASK = "task";
 	int TASK_ADD_CONTACT = 0;
diff --git a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
index fb6ede72d4cddda86ad12e77c5402eb5fb7d0d16..44bc5979e6b8725109dede1b22783d10a1a097a1 100644
--- a/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
+++ b/briar-core/src/org/briarproject/contact/ContactManagerImpl.java
@@ -114,15 +114,9 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 	}
 
 	@Override
-	public void setContactActive(ContactId c, boolean active)
+	public void setContactActive(Transaction txn, ContactId c, boolean active)
 			throws DbException {
-		Transaction txn = db.startTransaction(false);
-		try {
-			db.setContactActive(txn, c, active);
-			txn.setComplete();
-		} finally {
-			db.endTransaction(txn);
-		}
+		db.setContactActive(txn, c, active);
 	}
 
 	@Override
@@ -145,7 +139,8 @@ class ContactManagerImpl implements ContactManager, RemoveIdentityHook {
 		return exists;
 	}
 
-	private void removeContact(Transaction txn, ContactId c)
+	@Override
+	public void removeContact(Transaction txn, ContactId c)
 			throws DbException {
 		Contact contact = db.getContact(txn, c);
 		for (RemoveContactHook hook : removeHooks)
diff --git a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
index 169bfb7818f9d8002cbeac905dc9451584a00d7b..22698a22ff70aac5087158a3f17684aab0c87686 100644
--- a/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
+++ b/briar-core/src/org/briarproject/crypto/CryptoComponentImpl.java
@@ -87,6 +87,9 @@ class CryptoComponentImpl implements CryptoComponent {
 	// KDF labels for header key derivation
 	private static final byte[] A_HEADER = ascii("ALICE_HEADER_KEY");
 	private static final byte[] B_HEADER = ascii("BOB_HEADER_KEY");
+	// KDF labels for MAC key derivation
+	private static final byte[] A_MAC = ascii("ALICE_MAC_KEY");
+	private static final byte[] B_MAC = ascii("BOB_MAC_KEY");
 	// KDF label for key rotation
 	private static final byte[] ROTATE = ascii("ROTATE");
 
@@ -233,6 +236,11 @@ class CryptoComponentImpl implements CryptoComponent {
 		return new SecretKey(macKdf(master, alice ? A_INVITE : B_INVITE));
 	}
 
+	@Override
+	public SecretKey deriveMacKey(SecretKey master, boolean alice) {
+		return new SecretKey(macKdf(master, alice ? A_MAC : B_MAC));
+	}
+
 	@Override
 	public byte[] deriveSignatureNonce(SecretKey master,
 			boolean alice) {
diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java
index 50af8e754ad010b7d69a9b577afeeeefc8518b56..8f77539c79faee46489301b2fe982740ad089fa3 100644
--- a/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java
+++ b/briar-core/src/org/briarproject/introduction/IntroduceeEngine.java
@@ -22,6 +22,7 @@ import java.util.logging.Logger;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.introduction.IntroduceeAction.ACK;
 import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ABORT;
 import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_ACCEPT;
 import static org.briarproject.api.introduction.IntroduceeAction.LOCAL_DECLINE;
@@ -39,18 +40,22 @@ import static org.briarproject.api.introduction.IntroductionConstants.EXISTS;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.INTRODUCER;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
 import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
+import static org.briarproject.api.introduction.IntroductionConstants.OUR_MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
+import static org.briarproject.api.introduction.IntroductionConstants.OUR_SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
 import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
 import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.STATE;
 import static org.briarproject.api.introduction.IntroductionConstants.TASK;
 import static org.briarproject.api.introduction.IntroductionConstants.TASK_ABORT;
@@ -97,10 +102,10 @@ public class IntroduceeEngine
 				else return abortSession(currentState, localState);
 			}
 
+			List<BdfDictionary> messages = new ArrayList<BdfDictionary>(1);
 			if (action == LOCAL_ACCEPT || action == LOCAL_DECLINE) {
 				localState.put(STATE, nextState.getValue());
 				localState.put(ANSWERED, true);
-				List<BdfDictionary> messages = new ArrayList<BdfDictionary>(1);
 				// create the introduction response message
 				BdfDictionary msg = new BdfDictionary();
 				msg.put(TYPE, TYPE_RESPONSE);
@@ -118,17 +123,18 @@ public class IntroduceeEngine
 
 				if (nextState == AWAIT_ACK) {
 					localState.put(TASK, TASK_ADD_CONTACT);
-					// also send ACK, because we already have the other response
-					BdfDictionary ack = getAckMessage(localState);
-					messages.add(ack);
 				}
-				List<Event> events = Collections.emptyList();
-				return new StateUpdate<BdfDictionary, BdfDictionary>(false,
-						false,
-						localState, messages, events);
+			} else if (action == ACK) {
+				// just send ACK, don't update local state again
+				BdfDictionary ack = getAckMessage(localState);
+				messages.add(ack);
 			} else {
 				throw new IllegalArgumentException();
 			}
+			List<Event> events = Collections.emptyList();
+			return new StateUpdate<BdfDictionary, BdfDictionary>(false,
+					false,
+					localState, messages, events);
 		} catch (FormatException e) {
 			throw new IllegalArgumentException(e);
 		}
@@ -181,16 +187,14 @@ public class IntroduceeEngine
 				addResponseData(localState, msg);
 				if (nextState == AWAIT_ACK) {
 					localState.put(TASK, TASK_ADD_CONTACT);
-					messages = Collections
-							.singletonList(getAckMessage(localState));
-				} else {
-					messages = Collections.emptyList();
 				}
+				messages = Collections.emptyList();
 				events = Collections.emptyList();
 			}
 			// we already sent our ACK and now received the other one
 			else if (currentState == AWAIT_ACK) {
 				localState.put(TASK, TASK_ACTIVATE_CONTACT);
+				addAckData(localState, msg);
 				messages = Collections.emptyList();
 				events = Collections.emptyList();
 			}
@@ -240,6 +244,13 @@ public class IntroduceeEngine
 		}
 	}
 
+	private void addAckData(BdfDictionary localState, BdfDictionary msg)
+			throws FormatException {
+
+		localState.put(MAC, msg.getRaw(MAC));
+		localState.put(SIGNATURE, msg.getRaw(SIGNATURE));
+	}
+
 	private BdfDictionary getAckMessage(BdfDictionary localState)
 			throws FormatException {
 
@@ -247,6 +258,8 @@ public class IntroduceeEngine
 		m.put(TYPE, TYPE_ACK);
 		m.put(GROUP_ID, localState.getRaw(GROUP_ID));
 		m.put(SESSION_ID, localState.getRaw(SESSION_ID));
+		m.put(MAC, localState.getRaw(OUR_MAC));
+		m.put(SIGNATURE, localState.getRaw(OUR_SIGNATURE));
 
 		if (LOG.isLoggable(INFO)) {
 			LOG.info("Sending ACK " + " to " +
diff --git a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java
index 325fed6475c2d6cc5d8aaf50831bfcd3cfc38154..0223c14b3e26b8616dc8d940d0483e4c90939d06 100644
--- a/briar-core/src/org/briarproject/introduction/IntroduceeManager.java
+++ b/briar-core/src/org/briarproject/introduction/IntroduceeManager.java
@@ -13,6 +13,7 @@ import org.briarproject.api.crypto.KeyParser;
 import org.briarproject.api.crypto.PrivateKey;
 import org.briarproject.api.crypto.PublicKey;
 import org.briarproject.api.crypto.SecretKey;
+import org.briarproject.api.crypto.Signature;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.db.DatabaseComponent;
@@ -23,6 +24,8 @@ import org.briarproject.api.event.IntroductionSucceededEvent;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
+import org.briarproject.api.identity.LocalAuthor;
 import org.briarproject.api.properties.TransportProperties;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.GroupId;
@@ -32,6 +35,7 @@ import org.briarproject.api.system.Clock;
 
 import java.io.IOException;
 import java.security.GeneralSecurityException;
+import java.util.Arrays;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.logging.Logger;
@@ -40,6 +44,7 @@ import javax.inject.Inject;
 
 import static java.util.logging.Level.INFO;
 import static java.util.logging.Level.WARNING;
+import static org.briarproject.api.data.BdfDictionary.NULL_VALUE;
 import static org.briarproject.api.introduction.IntroduceeProtocolState.AWAIT_REQUEST;
 import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.api.introduction.IntroductionConstants.ADDED_CONTACT_ID;
@@ -51,18 +56,25 @@ import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_K
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.INTRODUCER;
 import static org.briarproject.api.introduction.IntroductionConstants.LOCAL_AUTHOR_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
+import static org.briarproject.api.introduction.IntroductionConstants.NONCE;
 import static org.briarproject.api.introduction.IntroductionConstants.NOT_OUR_RESPONSE;
+import static org.briarproject.api.introduction.IntroductionConstants.OUR_MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.OUR_PRIVATE_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.OUR_PUBLIC_KEY;
+import static org.briarproject.api.introduction.IntroductionConstants.OUR_SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.OUR_TIME;
+import static org.briarproject.api.introduction.IntroductionConstants.OUR_TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.REMOTE_AUTHOR_IS_US;
 import static org.briarproject.api.introduction.IntroductionConstants.ROLE;
 import static org.briarproject.api.introduction.IntroductionConstants.ROLE_INTRODUCEE;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.STATE;
 import static org.briarproject.api.introduction.IntroductionConstants.STORAGE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.TASK;
@@ -73,6 +85,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ABORT;
+import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
 
 class IntroduceeManager {
@@ -88,6 +101,7 @@ class IntroduceeManager {
 	private final TransportPropertyManager transportPropertyManager;
 	private final AuthorFactory authorFactory;
 	private final ContactManager contactManager;
+	private final IdentityManager identityManager;
 	private final IntroductionGroupFactory introductionGroupFactory;
 
 	@Inject
@@ -96,6 +110,7 @@ class IntroduceeManager {
 			CryptoComponent cryptoComponent,
 			TransportPropertyManager transportPropertyManager,
 			AuthorFactory authorFactory, ContactManager contactManager,
+			IdentityManager identityManager,
 			IntroductionGroupFactory introductionGroupFactory) {
 
 		this.messageSender = messageSender;
@@ -106,6 +121,7 @@ class IntroduceeManager {
 		this.transportPropertyManager = transportPropertyManager;
 		this.authorFactory = authorFactory;
 		this.contactManager = contactManager;
+		this.identityManager = identityManager;
 		this.introductionGroupFactory = introductionGroupFactory;
 	}
 
@@ -185,18 +201,19 @@ class IntroduceeManager {
 		byte[] privateKey = keyPair.getPrivate().getEncoded();
 		Map<TransportId, TransportProperties> transportProperties =
 				transportPropertyManager.getLocalProperties(txn);
+		BdfDictionary tp = encodeTransportProperties(transportProperties);
 
 		// update session state for later
 		state.put(ACCEPT, true);
 		state.put(OUR_TIME, now);
 		state.put(OUR_PUBLIC_KEY, publicKey);
 		state.put(OUR_PRIVATE_KEY, privateKey);
+		state.put(OUR_TRANSPORT, tp);
 
 		// define action
 		BdfDictionary localAction = new BdfDictionary();
 		localAction.put(TYPE, TYPE_RESPONSE);
-		localAction.put(TRANSPORT,
-				encodeTransportProperties(transportProperties));
+		localAction.put(TRANSPORT, tp);
 		localAction.put(MESSAGE_TIME, timestamp);
 
 		// start engine and process its state update
@@ -258,11 +275,12 @@ class IntroduceeManager {
 	private void performTasks(Transaction txn, BdfDictionary localState)
 			throws FormatException, DbException {
 
-		if (!localState.containsKey(TASK)) return;
+		if (!localState.containsKey(TASK) || localState.get(TASK) == NULL_VALUE)
+			return;
 
 		// remember task and remove it from localState
 		long task = localState.getLong(TASK);
-		localState.put(TASK, BdfDictionary.NULL_VALUE);
+		localState.put(TASK, NULL_VALUE);
 
 		if (task == TASK_ADD_CONTACT) {
 			if (localState.getBoolean(EXISTS)) {
@@ -306,20 +324,59 @@ class IntroduceeManager {
 				secretKey = cryptoComponent
 						.deriveMasterSecret(theirEphemeralKey, keyPair, alice);
 			} catch (GeneralSecurityException e) {
-				if (LOG.isLoggable(WARNING))
-					LOG.log(WARNING, e.toString(), e);
 				// we can not continue without the shared secret
-				throw new FormatException();
+				throw new DbException(e);
+			}
+
+			// Derive two nonces and a MAC key from the secret master key
+			byte[] ourNonce =
+					cryptoComponent.deriveSignatureNonce(secretKey, alice);
+			byte[] theirNonce =
+					cryptoComponent.deriveSignatureNonce(secretKey, !alice);
+			SecretKey macKey = cryptoComponent.deriveMacKey(secretKey, alice);
+			SecretKey theirMacKey =
+					cryptoComponent.deriveMacKey(secretKey, !alice);
+
+			// Save the other nonce and MAC key for the verification
+			localState.put(NONCE, theirNonce);
+			localState.put(MAC_KEY, theirMacKey.getBytes());
+
+			// Sign our nonce with our long-term identity public key
+			AuthorId localAuthorId =
+					new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID));
+			LocalAuthor author =
+					identityManager.getLocalAuthor(txn, localAuthorId);
+			Signature signature = cryptoComponent.getSignature();
+			KeyParser sigParser = cryptoComponent.getSignatureKeyParser();
+			try {
+				PrivateKey privKey =
+						sigParser.parsePrivateKey(author.getPrivateKey());
+				signature.initSign(privKey);
+			} catch (GeneralSecurityException e) {
+				// we can not continue without the signature
+				throw new DbException(e);
 			}
+			signature.update(ourNonce);
+			byte[] sig = signature.sign();
 
 			// The agreed timestamp is the minimum of the peers' timestamps
 			long ourTime = localState.getLong(OUR_TIME);
 			long theirTime = localState.getLong(TIME);
 			long timestamp = Math.min(ourTime, theirTime);
 
-			// Add the contact to the database
-			AuthorId localAuthorId =
-					new AuthorId(localState.getRaw(LOCAL_AUTHOR_ID));
+			// Calculate a MAC over identity public key, ephemeral public key,
+			// transport properties and timestamp.
+			BdfDictionary tp = localState.getDictionary(OUR_TRANSPORT);
+			BdfList toSignList = BdfList.of(author.getPublicKey(),
+					publicKeyBytes, tp, ourTime);
+			byte[] toSign = clientHelper.toByteArray(toSignList);
+			byte[] mac = cryptoComponent.mac(macKey, toSign);
+
+			// Add MAC and signature to localState, so it can be included in ACK
+			localState.put(OUR_MAC, mac);
+			localState.put(OUR_SIGNATURE, sig);
+
+			// Add the contact to the database as inactive
 			Author remoteAuthor = authorFactory
 					.createAuthor(localState.getString(NAME),
 							localState.getRaw(PUBLIC_KEY));
@@ -339,7 +396,16 @@ class IntroduceeManager {
 			// delete the ephemeral private key by overwriting with NULL value
 			// this ensures future ephemeral keys can not be recovered when
 			// this device should gets compromised
-			localState.put(OUR_PRIVATE_KEY, BdfDictionary.NULL_VALUE);
+			localState.put(OUR_PRIVATE_KEY, NULL_VALUE);
+
+			// define next action: Send ACK
+			BdfDictionary localAction = new BdfDictionary();
+			localAction.put(TYPE, TYPE_ACK);
+
+			// start engine and process its state update
+			IntroduceeEngine engine = new IntroduceeEngine();
+			processStateUpdate(txn, null,
+					engine.onLocalAction(localState, localAction));
 		}
 
 		// we sent and received an ACK, so activate contact
@@ -347,13 +413,57 @@ class IntroduceeManager {
 			if (!localState.getBoolean(EXISTS) &&
 					localState.containsKey(ADDED_CONTACT_ID)) {
 
+				LOG.info("Verifying Signature...");
+
+				byte[] nonce = localState.getRaw(NONCE);
+				byte[] sig = localState.getRaw(SIGNATURE);
+				byte[] keyBytes = localState.getRaw(PUBLIC_KEY);
+				try {
+					// Parse the public key
+					KeyParser keyParser = cryptoComponent.getSignatureKeyParser();
+					PublicKey key = keyParser.parsePublicKey(keyBytes);
+					// Verify the signature
+					Signature signature = cryptoComponent.getSignature();
+					signature.initVerify(key);
+					signature.update(nonce);
+					if (!signature.verify(sig)) {
+						LOG.warning("Invalid nonce signature in ACK");
+						throw new GeneralSecurityException();
+					}
+				} catch (GeneralSecurityException e) {
+					if (LOG.isLoggable(WARNING))
+						LOG.log(WARNING, e.toString(), e);
+					// we can not continue without verifying the signature
+					throw new DbException(e);
+				}
+
+				LOG.info("Verifying MAC...");
+
+				// get MAC and MAC key from session state
+				byte[] mac = localState.getRaw(MAC);
+				byte[] macKeyBytes = localState.getRaw(MAC_KEY);
+				SecretKey macKey = new SecretKey(macKeyBytes);
+
+				// get MAC data and calculate a new MAC with stored key
+				byte[] pubKey = localState.getRaw(PUBLIC_KEY);
+				byte[] ePubKey = localState.getRaw(E_PUBLIC_KEY);
+				BdfDictionary tp = localState.getDictionary(TRANSPORT);
+				long timestamp = localState.getLong(TIME);
+				BdfList toSignList = BdfList.of(pubKey, ePubKey, tp, timestamp);
+				byte[] toSign = clientHelper.toByteArray(toSignList);
+				byte[] calculatedMac = cryptoComponent.mac(macKey, toSign);
+				if (!Arrays.equals(mac, calculatedMac)) {
+					LOG.warning("Received ACK with invalid MAC");
+					throw new DbException();
+				}
+
 				LOG.info("Activating Contact...");
 
 				ContactId contactId = new ContactId(
 						localState.getLong(ADDED_CONTACT_ID).intValue());
 
 				// activate and show contact in contact list
-				db.setContactActive(txn, contactId, true);
+				contactManager.setContactActive(txn, contactId, true);
 
 				// broadcast event informing of successful introduction
 				Contact contact = db.getContact(txn, contactId);
@@ -371,7 +481,7 @@ class IntroduceeManager {
 				LOG.info("Deleting added contact due to abort...");
 				ContactId contactId = new ContactId(
 						localState.getLong(ADDED_CONTACT_ID).intValue());
-				contactManager.removeContact(contactId);
+				contactManager.removeContact(txn, contactId);
 			}
 		}
 
diff --git a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
index 7eacaccc839485c108841c03f2d068663e3cc7f5..3473ad61364779980c9868840ab3f9ad00f2c997 100644
--- a/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
+++ b/briar-core/src/org/briarproject/introduction/IntroductionValidator.java
@@ -1,9 +1,9 @@
 package org.briarproject.introduction;
 
 import org.briarproject.api.FormatException;
+import org.briarproject.api.clients.BdfMessageContext;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.SessionId;
-import org.briarproject.api.clients.BdfMessageContext;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
@@ -15,15 +15,19 @@ import org.briarproject.clients.BdfMessageValidator;
 import static org.briarproject.api.TransportId.MAX_TRANSPORT_ID_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_ID;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
@@ -150,13 +154,20 @@ class IntroductionValidator extends BdfMessageValidator {
 		return d;
 	}
 
-	private BdfDictionary validateAck(BdfList message)
-			throws FormatException {
+	private BdfDictionary validateAck(BdfList message) throws FormatException {
+		checkSize(message, 4);
 
-		checkSize(message, 2);
+		byte[] mac = message.getRaw(2);
+		checkLength(mac, 1, MAC_LENGTH);
+
+		byte[] sig = message.getRaw(3);
+		checkLength(sig, 1, MAX_SIGNATURE_LENGTH);
 
 		// Return the metadata
-		return new BdfDictionary();
+		BdfDictionary d = new BdfDictionary();
+		d.put(MAC, mac);
+		d.put(SIGNATURE, sig);
+		return d;
 	}
 
 	private BdfDictionary validateAbort(BdfList message)
diff --git a/briar-core/src/org/briarproject/introduction/MessageSender.java b/briar-core/src/org/briarproject/introduction/MessageSender.java
index 9327dc9e26c11b700b33fb0ee4ae3f1489be8bbd..bfa8138d8f4a6775599615e204223169930fc351 100644
--- a/briar-core/src/org/briarproject/introduction/MessageSender.java
+++ b/briar-core/src/org/briarproject/introduction/MessageSender.java
@@ -19,11 +19,13 @@ import javax.inject.Inject;
 import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.MESSAGE_TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
@@ -108,12 +110,12 @@ public class MessageSender {
 			list.add(d.getRaw(E_PUBLIC_KEY));
 			list.add(d.getDictionary(TRANSPORT));
 		}
-		// TODO Sign the response, see #256
 		return list;
 	}
 
 	private BdfList encodeAck(BdfDictionary d) throws FormatException {
-		return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID));
+		return BdfList.of(TYPE_ACK, d.getRaw(SESSION_ID),
+				d.getRaw(MAC), d.getRaw(SIGNATURE));
 	}
 
 	private BdfList encodeAbort(BdfDictionary d) throws FormatException {
diff --git a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java
index 8b7e1afe340f2fb98087213712809f5762172505..0a91bd6864a355e54feb799be4cf897bd45f83d6 100644
--- a/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java
+++ b/briar-tests/src/org/briarproject/introduction/IntroduceeManagerTest.java
@@ -5,6 +5,7 @@ import org.briarproject.TestUtils;
 import org.briarproject.api.Bytes;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.contact.Contact;
 import org.briarproject.api.contact.ContactId;
 import org.briarproject.api.contact.ContactManager;
@@ -18,8 +19,8 @@ import org.briarproject.api.db.Transaction;
 import org.briarproject.api.identity.Author;
 import org.briarproject.api.identity.AuthorFactory;
 import org.briarproject.api.identity.AuthorId;
+import org.briarproject.api.identity.IdentityManager;
 import org.briarproject.api.introduction.IntroduceeProtocolState;
-import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.properties.TransportPropertyManager;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
@@ -63,34 +64,33 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUEST;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
 import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
 public class IntroduceeManagerTest extends BriarTestCase {
 
-	final Mockery context;
-	final IntroduceeManager introduceeManager;
-	final DatabaseComponent db;
-	final CryptoComponent cryptoComponent;
-	final ClientHelper clientHelper;
-	final IntroductionGroupFactory introductionGroupFactory;
-	final MessageSender messageSender;
-	final TransportPropertyManager transportPropertyManager;
-	final AuthorFactory authorFactory;
-	final ContactManager contactManager;
-	final Clock clock;
-	final Contact introducer;
-	final Contact introducee1;
-	final Contact introducee2;
-	final Group localGroup1;
-	final Group introductionGroup1;
-	final Group introductionGroup2;
-	final Transaction txn;
-	final long time = 42L;
-	final Message localStateMessage;
-	final ClientId clientId;
-	final SessionId sessionId;
-	final Message message1;
+	private final Mockery context;
+	private final IntroduceeManager introduceeManager;
+	private final DatabaseComponent db;
+	private final CryptoComponent cryptoComponent;
+	private final ClientHelper clientHelper;
+	private final IntroductionGroupFactory introductionGroupFactory;
+	private final MessageSender messageSender;
+	private final TransportPropertyManager transportPropertyManager;
+	private final AuthorFactory authorFactory;
+	private final ContactManager contactManager;
+	private final IdentityManager identityManager;
+	private final Clock clock;
+	private final Contact introducer;
+	private final Contact introducee1;
+	private final Contact introducee2;
+	private final Group localGroup1;
+	private final Group introductionGroup1;
+	private final Transaction txn;
+	private final long time = 42L;
+	private final Message localStateMessage;
+	private final ClientId clientId;
+	private final SessionId sessionId;
+	private final Message message1;
 
 	public IntroduceeManagerTest() {
 		context = new Mockery();
@@ -105,10 +105,12 @@ public class IntroduceeManagerTest extends BriarTestCase {
 		transportPropertyManager = context.mock(TransportPropertyManager.class);
 		authorFactory = context.mock(AuthorFactory.class);
 		contactManager = context.mock(ContactManager.class);
+		identityManager = context.mock(IdentityManager.class);
 
 		introduceeManager = new IntroduceeManager(messageSender, db,
 				clientHelper, clock, cryptoComponent, transportPropertyManager,
-				authorFactory, contactManager, introductionGroupFactory);
+				authorFactory, contactManager, identityManager,
+				introductionGroupFactory);
 
 		AuthorId authorId0 = new AuthorId(TestUtils.getRandomId());
 		Author author0 = new Author(authorId0, "Introducer",
@@ -138,8 +140,6 @@ public class IntroduceeManagerTest extends BriarTestCase {
 				clientId, new byte[0]);
 		introductionGroup1 = new Group(new GroupId(TestUtils.getRandomId()),
 				clientId, new byte[0]);
-		introductionGroup2 = new Group(new GroupId(TestUtils.getRandomId()),
-				clientId, new byte[0]);
 
 		sessionId = new SessionId(TestUtils.getRandomId());
 		localStateMessage = new Message(
diff --git a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java
index 5399abdbcaf9033663549b5080aa6637d79c607e..fa0fee5e47c9dc2bbaa1a1e172e8b681baeea194 100644
--- a/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java
+++ b/briar-tests/src/org/briarproject/introduction/IntroductionValidatorTest.java
@@ -5,11 +5,11 @@ import org.briarproject.TestUtils;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.TransportId;
 import org.briarproject.api.clients.ClientHelper;
+import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.data.BdfList;
 import org.briarproject.api.data.MetadataEncoder;
-import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
@@ -24,13 +24,16 @@ import java.io.IOException;
 
 import static org.briarproject.api.identity.AuthorConstants.MAX_AUTHOR_NAME_LENGTH;
 import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
+import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.ACCEPT;
 import static org.briarproject.api.introduction.IntroductionConstants.E_PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.MSG;
 import static org.briarproject.api.introduction.IntroductionConstants.NAME;
 import static org.briarproject.api.introduction.IntroductionConstants.PUBLIC_KEY;
 import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.TIME;
 import static org.briarproject.api.introduction.IntroductionConstants.TRANSPORT;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
@@ -40,6 +43,7 @@ import static org.briarproject.api.introduction.IntroductionConstants.TYPE_REQUE
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_RESPONSE;
 import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
 import static org.briarproject.api.sync.SyncConstants.MAX_MESSAGE_BODY_LENGTH;
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 
@@ -281,19 +285,18 @@ public class IntroductionValidatorTest extends BriarTestCase {
 
 	@Test
 	public void testValidateProperIntroductionAck() throws IOException {
-		final byte[] sessionId = TestUtils.getRandomId();
-
-		BdfDictionary msg = new BdfDictionary();
-		msg.put(TYPE, TYPE_ACK);
-		msg.put(SESSION_ID, sessionId);
-
-		BdfList body = BdfList.of(msg.getLong(TYPE), msg.getRaw(SESSION_ID));
+		byte[] sessionId = TestUtils.getRandomId();
+		byte[] mac = TestUtils.getRandomBytes(42);
+		byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
+		BdfList body = BdfList.of(TYPE_ACK, sessionId, mac, sig);
 
 		BdfDictionary result =
 				validator.validateMessage(message, group, body).getDictionary();
 
 		assertEquals(Long.valueOf(TYPE_ACK), result.getLong(TYPE));
-		assertEquals(sessionId, result.getRaw(SESSION_ID));
+		assertArrayEquals(sessionId, result.getRaw(SESSION_ID));
+		assertArrayEquals(mac, result.getRaw(MAC));
+		assertArrayEquals(sig, result.getRaw(SIGNATURE));
 		context.assertIsSatisfied();
 	}
 
diff --git a/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java b/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java
index 8809fd0a1a5fdd6cbbcf95a9a84f2cbfbb83ca09..bbc7cf13d090508bf57da9d31c1e4abd2c2d2184 100644
--- a/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java
+++ b/briar-tests/src/org/briarproject/introduction/MessageSenderTest.java
@@ -5,7 +5,7 @@ import org.briarproject.TestUtils;
 import org.briarproject.api.FormatException;
 import org.briarproject.api.clients.ClientHelper;
 import org.briarproject.api.clients.MessageQueueManager;
-import org.briarproject.api.clients.PrivateGroupFactory;
+import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.data.BdfDictionary;
 import org.briarproject.api.data.BdfEntry;
 import org.briarproject.api.data.BdfList;
@@ -14,7 +14,6 @@ import org.briarproject.api.db.DatabaseComponent;
 import org.briarproject.api.db.DbException;
 import org.briarproject.api.db.Metadata;
 import org.briarproject.api.db.Transaction;
-import org.briarproject.api.clients.SessionId;
 import org.briarproject.api.sync.ClientId;
 import org.briarproject.api.sync.Group;
 import org.briarproject.api.sync.GroupId;
@@ -24,26 +23,27 @@ import org.jmock.Mockery;
 import org.junit.Test;
 
 import static junit.framework.Assert.assertFalse;
+import static org.briarproject.api.identity.AuthorConstants.MAX_SIGNATURE_LENGTH;
 import static org.briarproject.api.introduction.IntroductionConstants.GROUP_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.MAC;
 import static org.briarproject.api.introduction.IntroductionConstants.SESSION_ID;
+import static org.briarproject.api.introduction.IntroductionConstants.SIGNATURE;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE;
 import static org.briarproject.api.introduction.IntroductionConstants.TYPE_ACK;
 
 public class MessageSenderTest extends BriarTestCase {
 
-	final Mockery context;
-	final MessageSender messageSender;
-	final DatabaseComponent db;
-	final PrivateGroupFactory privateGroupFactory;
-	final ClientHelper clientHelper;
-	final MetadataEncoder metadataEncoder;
-	final MessageQueueManager messageQueueManager;
-	final Clock clock;
+	private final Mockery context;
+	private final MessageSender messageSender;
+	private final DatabaseComponent db;
+	private final ClientHelper clientHelper;
+	private final MetadataEncoder metadataEncoder;
+	private final MessageQueueManager messageQueueManager;
+	private final Clock clock;
 
 	public MessageSenderTest() {
 		context = new Mockery();
 		db = context.mock(DatabaseComponent.class);
-		privateGroupFactory = context.mock(PrivateGroupFactory.class);
 		clientHelper = context.mock(ClientHelper.class);
 		metadataEncoder =
 				context.mock(MetadataEncoder.class);
@@ -59,17 +59,22 @@ public class MessageSenderTest extends BriarTestCase {
 	@Test
 	public void testSendMessage() throws DbException, FormatException {
 		final Transaction txn = new Transaction(null, false);
-		final Group privateGroup = new Group(new GroupId(TestUtils.getRandomId()),
-				new ClientId(TestUtils.getRandomId()), new byte[0]);
+		final Group privateGroup =
+				new Group(new GroupId(TestUtils.getRandomId()),
+						new ClientId(TestUtils.getRandomId()), new byte[0]);
 		final SessionId sessionId = new SessionId(TestUtils.getRandomId());
+		byte[] mac = TestUtils.getRandomBytes(42);
+		byte[] sig = TestUtils.getRandomBytes(MAX_SIGNATURE_LENGTH);
 		final long time = 42L;
 		final BdfDictionary msg = BdfDictionary.of(
 				new BdfEntry(TYPE, TYPE_ACK),
 				new BdfEntry(GROUP_ID, privateGroup.getId()),
-				new BdfEntry(SESSION_ID, sessionId)
+				new BdfEntry(SESSION_ID, sessionId),
+				new BdfEntry(MAC, mac),
+				new BdfEntry(SIGNATURE, sig)
 		);
 		final BdfList bodyList =
-				BdfList.of(TYPE_ACK, msg.getRaw(SESSION_ID));
+				BdfList.of(TYPE_ACK, sessionId.getBytes(), mac, sig);
 		final byte[] body = TestUtils.getRandomBytes(8);
 		final Metadata metadata = new Metadata();