diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
index 97afdc133e1a672ecfcd5bda9959aa64ca619e2f..1e26a7b3d05f079bab61e44b61465bd05b223115 100644
--- a/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
+++ b/bramble-api/src/main/java/org/briarproject/bramble/api/transport/KeyManager.java
@@ -16,13 +16,21 @@ public interface KeyManager {
 
 	/**
 	 * Informs the key manager that a new contact has been added. Derives and
-	 * stores transport keys for communicating with the contact.
+	 * stores a set of transport keys for communicating with the contact over
+	 * each transport.
+	 * <p/>
 	 * {@link StreamContext StreamContexts} for the contact can be created
 	 * after this method has returned.
 	 */
 	void addContact(Transaction txn, ContactId c, SecretKey master,
 			long timestamp, boolean alice) throws DbException;
 
+	/**
+	 * Derives and stores a set of unbound transport keys for each transport.
+	 */
+	void addUnboundKeys(Transaction txn, SecretKey master, long timestamp,
+			boolean alice) throws DbException;
+
 	/**
 	 * Returns a {@link StreamContext} for sending a stream to the given
 	 * contact over the given transport, or null if an error occurs or the
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
index 25e0681c963778bf649bef07e801a2a06474314a..afac362ced269204d1a9c0376ea12962513d241e 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/contact/ContactManagerImpl.java
@@ -50,7 +50,7 @@ class ContactManagerImpl implements ContactManager {
 
 	@Override
 	public ContactId addContact(Transaction txn, Author remote, AuthorId local,
-			SecretKey master,long timestamp, boolean alice, boolean verified,
+			SecretKey master, long timestamp, boolean alice, boolean verified,
 			boolean active) throws DbException {
 		ContactId c = db.addContact(txn, remote, local, verified, active);
 		keyManager.addContact(txn, c, master, timestamp, alice);
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
index d0c6fd709d619911f6cc9862b99947df3eb82bfe..be50963e0df00656b0216f0ebe6016ed0e80064f 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/KeyManagerImpl.java
@@ -104,6 +104,13 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
 			m.addContact(txn, c, master, timestamp, alice);
 	}
 
+	@Override
+	public void addUnboundKeys(Transaction txn, SecretKey master,
+			long timestamp, boolean alice) throws DbException {
+		for (TransportKeyManager m : managers.values())
+			m.addUnboundKeys(txn, master, timestamp, alice);
+	}
+
 	@Override
 	public StreamContext getStreamContext(ContactId c, TransportId t)
 			throws DbException {
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
index 6aa6d360fe2b24af7a643167347c955f75e90bdb..8c9b03fd8b8bd1eeaba7c7419e20c118f76ecc90 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManager.java
@@ -17,6 +17,9 @@ interface TransportKeyManager {
 	void addContact(Transaction txn, ContactId c, SecretKey master,
 			long timestamp, boolean alice) throws DbException;
 
+	void addUnboundKeys(Transaction txn, SecretKey master, long timestamp,
+			boolean alice) throws DbException;
+
 	void removeContact(ContactId c);
 
 	@Nullable
diff --git a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
index 1220beee580ea7af59c0b682af94546fd80e0068..7b4893e5617b27093b45d88edeb1b65c6fcc0561 100644
--- a/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
+++ b/bramble-core/src/main/java/org/briarproject/bramble/transport/TransportKeyManagerImpl.java
@@ -172,6 +172,18 @@ class TransportKeyManagerImpl implements TransportKeyManager {
 	@Override
 	public void addContact(Transaction txn, ContactId c, SecretKey master,
 			long timestamp, boolean alice) throws DbException {
+		deriveAndAddKeys(txn, c, master, timestamp, alice);
+	}
+
+	@Override
+	public void addUnboundKeys(Transaction txn, SecretKey master,
+			long timestamp, boolean alice) throws DbException {
+		deriveAndAddKeys(txn, null, master, timestamp, alice);
+	}
+
+	private void deriveAndAddKeys(Transaction txn, @Nullable ContactId c,
+			SecretKey master, long timestamp, boolean alice)
+			throws DbException {
 		lock.lock();
 		try {
 			// Work out what rotation period the timestamp belongs to
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
index edf073a4f6df64a53b1dceda71f6b06883b00f92..7320d60ce7b3aa5a1ec57c01f03fff81e2d0dd9f 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/KeyManagerImplTest.java
@@ -113,6 +113,21 @@ public class KeyManagerImplTest extends BrambleTestCase {
 		context.assertIsSatisfied();
 	}
 
+	@Test
+	public void testAddUnboundKeys() throws Exception {
+		SecretKey secretKey = getSecretKey();
+		long timestamp = System.currentTimeMillis();
+		boolean alice = new Random().nextBoolean();
+
+		context.checking(new Expectations() {{
+			oneOf(transportKeyManager).addUnboundKeys(txn, secretKey,
+					timestamp, alice);
+		}});
+
+		keyManager.addUnboundKeys(txn, secretKey, timestamp, alice);
+		context.assertIsSatisfied();
+	}
+
 	@Test
 	public void testGetStreamContextForInactiveContact() throws Exception {
 		assertEquals(null,
diff --git a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
index 213400f1e6096181c5c18a8fdc3bd437d64a4c6e..b926707df4effe116569cfd02e5d948ed4d87c24 100644
--- a/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
+++ b/bramble-core/src/test/java/org/briarproject/bramble/transport/TransportKeyManagerImplTest.java
@@ -30,7 +30,6 @@ import java.util.concurrent.Executor;
 import java.util.concurrent.ScheduledExecutorService;
 
 import static java.util.Arrays.asList;
-import static java.util.Collections.singletonList;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static org.briarproject.bramble.api.transport.TransportConstants.MAX_CLOCK_DIFFERENCE;
 import static org.briarproject.bramble.api.transport.TransportConstants.PROTOCOL_VERSION;
@@ -57,6 +56,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	private final ContactId contactId = new ContactId(123);
 	private final ContactId contactId1 = new ContactId(234);
 	private final KeySetId keySetId = new KeySetId(345);
+	private final KeySetId keySetId1 = new KeySetId(456);
+	private final KeySetId keySetId2 = new KeySetId(567);
 	private final SecretKey tagKey = TestUtils.getSecretKey();
 	private final SecretKey headerKey = TestUtils.getSecretKey();
 	private final SecretKey masterKey = TestUtils.getSecretKey();
@@ -66,11 +67,14 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	public void testKeysAreRotatedAtStartup() throws Exception {
 		TransportKeys shouldRotate = createTransportKeys(900, 0);
 		TransportKeys shouldNotRotate = createTransportKeys(1000, 0);
+		TransportKeys shouldRotate1 = createTransportKeys(999, 0);
 		Collection<KeySet> loaded = asList(
 				new KeySet(keySetId, contactId, shouldRotate),
-				new KeySet(keySetId, contactId1, shouldNotRotate)
+				new KeySet(keySetId1, contactId1, shouldNotRotate),
+				new KeySet(keySetId2, null, shouldRotate1)
 		);
 		TransportKeys rotated = createTransportKeys(1000, 0);
+		TransportKeys rotated1 = createTransportKeys(1000, 0);
 		Transaction txn = new Transaction(null, false);
 
 		context.checking(new Expectations() {{
@@ -85,6 +89,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			will(returnValue(rotated));
 			oneOf(transportCrypto).rotateTransportKeys(shouldNotRotate, 1000);
 			will(returnValue(shouldNotRotate));
+			oneOf(transportCrypto).rotateTransportKeys(shouldRotate1, 1000);
+			will(returnValue(rotated1));
 			// Encode the tags (3 sets per contact)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(6).of(transportCrypto).encodeTag(
@@ -93,8 +99,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated
-			oneOf(db).updateTransportKeys(txn,
-					singletonList(new KeySet(keySetId, contactId, rotated)));
+			oneOf(db).updateTransportKeys(txn, asList(
+					new KeySet(keySetId, contactId, rotated),
+					new KeySet(keySetId2, null, rotated1))
+			);
 			// Schedule key rotation at the start of the next rotation period
 			oneOf(scheduler).schedule(with(any(Runnable.class)),
 					with(rotationPeriodLength - 1), with(MILLISECONDS));
@@ -144,6 +152,36 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				alice);
 	}
 
+	@Test
+	public void testKeysAreRotatedWhenAddingUnboundKeys() throws Exception {
+		boolean alice = random.nextBoolean();
+		TransportKeys transportKeys = createTransportKeys(999, 0);
+		TransportKeys rotated = createTransportKeys(1000, 0);
+		Transaction txn = new Transaction(null, false);
+
+		context.checking(new Expectations() {{
+			oneOf(transportCrypto).deriveTransportKeys(transportId, masterKey,
+					999, alice);
+			will(returnValue(transportKeys));
+			// Get the current time (1 ms after start of rotation period 1000)
+			oneOf(clock).currentTimeMillis();
+			will(returnValue(rotationPeriodLength * 1000 + 1));
+			// Rotate the transport keys
+			oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
+			will(returnValue(rotated));
+			// Save the keys
+			oneOf(db).addTransportKeys(txn, null, rotated);
+			will(returnValue(keySetId));
+		}});
+
+		TransportKeyManager transportKeyManager = new TransportKeyManagerImpl(
+				db, transportCrypto, dbExecutor, scheduler, clock, transportId,
+				maxLatency);
+		// The timestamp is 1 ms before the start of rotation period 1000
+		long timestamp = rotationPeriodLength * 1000 - 1;
+		transportKeyManager.addUnboundKeys(txn, masterKey, timestamp, alice);
+	}
+
 	@Test
 	public void testOutgoingStreamContextIsNullIfContactIsNotFound()
 			throws Exception {
@@ -353,9 +391,13 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 	@Test
 	public void testKeysAreRotatedToCurrentPeriod() throws Exception {
 		TransportKeys transportKeys = createTransportKeys(1000, 0);
-		Collection<KeySet> loaded =
-				singletonList(new KeySet(keySetId, contactId, transportKeys));
+		TransportKeys transportKeys1 = createTransportKeys(1000, 0);
+		Collection<KeySet> loaded = asList(
+				new KeySet(keySetId, contactId, transportKeys),
+				new KeySet(keySetId1, null, transportKeys1)
+		);
 		TransportKeys rotated = createTransportKeys(1001, 0);
+		TransportKeys rotated1 = createTransportKeys(1001, 0);
 		Transaction txn = new Transaction(null, false);
 		Transaction txn1 = new Transaction(null, false);
 
@@ -369,6 +411,8 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			// Rotate the transport keys (the keys are unaffected)
 			oneOf(transportCrypto).rotateTransportKeys(transportKeys, 1000);
 			will(returnValue(transportKeys));
+			oneOf(transportCrypto).rotateTransportKeys(transportKeys1, 1000);
+			will(returnValue(transportKeys1));
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(transportCrypto).encodeTag(
@@ -392,6 +436,9 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 			oneOf(transportCrypto).rotateTransportKeys(
 					with(any(TransportKeys.class)), with(1001L));
 			will(returnValue(rotated));
+			oneOf(transportCrypto).rotateTransportKeys(
+					with(any(TransportKeys.class)), with(1001L));
+			will(returnValue(rotated1));
 			// Encode the tags (3 sets)
 			for (long i = 0; i < REORDERING_WINDOW_SIZE; i++) {
 				exactly(3).of(transportCrypto).encodeTag(
@@ -400,8 +447,10 @@ public class TransportKeyManagerImplTest extends BrambleMockTestCase {
 				will(new EncodeTagAction());
 			}
 			// Save the keys that were rotated
-			oneOf(db).updateTransportKeys(txn1,
-					singletonList(new KeySet(keySetId, contactId, rotated)));
+			oneOf(db).updateTransportKeys(txn1, asList(
+					new KeySet(keySetId, contactId, rotated),
+					new KeySet(keySetId1, null, rotated1)
+			));
 			// Schedule key rotation at the start of the next rotation period
 			oneOf(scheduler).schedule(with(any(Runnable.class)),
 					with(rotationPeriodLength), with(MILLISECONDS));