diff --git a/api/net/sf/briar/api/db/DatabaseComponent.java b/api/net/sf/briar/api/db/DatabaseComponent.java
index f646a9e4120ffbdfd4c86532c6cc3dac122797ff..ca142d39df885c9c6a97f15e9a3e87a4010626c3 100644
--- a/api/net/sf/briar/api/db/DatabaseComponent.java
+++ b/api/net/sf/briar/api/db/DatabaseComponent.java
@@ -32,8 +32,8 @@ public interface DatabaseComponent {
 	/** Waits for any open transactions to finish and closes the database. */
 	void close() throws DbException;
 
-	/** Adds a new contact to the database. */
-	void addContact(ContactId c) throws DbException;
+	/** Adds a new contact to the database and returns an ID for the contact. */
+	ContactId addContact() throws DbException;
 
 	/** Adds a locally generated message to the database. */
 	void addLocallyGeneratedMessage(Message m) throws DbException;
@@ -44,6 +44,9 @@ public interface DatabaseComponent {
 	 */
 	void generateBundle(ContactId c, Bundle b) throws DbException;
 
+	/** Returns the IDs of all contacts. */
+	Set<ContactId> getContacts() throws DbException;
+
 	/** Returns the user's rating for the given author. */
 	Rating getRating(AuthorId a) throws DbException;
 
diff --git a/components/net/sf/briar/db/Database.java b/components/net/sf/briar/db/Database.java
index 9246874b05aa801263482f43c8770a83d32c223f..a0e611a8ef3a784c18e46c4d09f6069910b6460b 100644
--- a/components/net/sf/briar/db/Database.java
+++ b/components/net/sf/briar/db/Database.java
@@ -75,19 +75,19 @@ interface Database<T> {
 	void addBatchToAck(T txn, ContactId c, BatchId b) throws DbException;
 
 	/**
-	 * Returns false if the given message is already in the database. Otherwise
-	 * stores the message and returns true.
+	 * Adds a new contact to the database and returns an ID for the contact.
 	 * <p>
-	 * Locking: messages write.
+	 * Locking: contacts write, messageStatuses write.
 	 */
-	boolean addMessage(T txn, Message m) throws DbException;
+	ContactId addContact(T txn) throws DbException;
 
 	/**
-	 * Adds a new contact to the database.
+	 * Returns false if the given message is already in the database. Otherwise
+	 * stores the message and returns true.
 	 * <p>
-	 * Locking: contacts write, messageStatuses write.
+	 * Locking: messages write.
 	 */
-	void addContact(T txn, ContactId c) throws DbException;
+	boolean addMessage(T txn, Message m) throws DbException;
 
 	/**
 	 * Records a sent batch as needing to be acknowledged.
diff --git a/components/net/sf/briar/db/JdbcDatabase.java b/components/net/sf/briar/db/JdbcDatabase.java
index 1270687995947f2b6d2e9e1b46405f0b170b14cb..e0440e5ce168334869546ed8170123cc828f4382 100644
--- a/components/net/sf/briar/db/JdbcDatabase.java
+++ b/components/net/sf/briar/db/JdbcDatabase.java
@@ -126,7 +126,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		+ " (bundleId XXXX NOT NULL,"
 		+ " contactId INT NOT NULL,"
 		+ " timestamp BIGINT NOT NULL,"
-		+ " PRIMARY KEY (bundleId),"
+		+ " PRIMARY KEY (bundleId, contactId),"
 		+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
 		+ " ON DELETE CASCADE)";
 
@@ -363,10 +363,24 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public void addContact(Connection txn, ContactId c) throws DbException {
+	public ContactId addContact(Connection txn) throws DbException {
 		PreparedStatement ps = null;
+		ResultSet rs = null;
 		try {
-			String sql = "INSERT INTO contacts"
+			// Get the highest existing contact ID
+			String sql = "SELECT contactId FROM contacts"
+				+ " ORDER BY contactId DESC LIMIT ?";
+			ps = txn.prepareStatement(sql);
+			ps.setInt(1, 1);
+			rs = ps.executeQuery();
+			int nextId = rs.next() ? rs.getInt(1) + 1 : 1;
+			ContactId c = new ContactId(nextId);
+			boolean more = rs.next();
+			assert !more;
+			rs.close();
+			ps.close();
+			// Create a new contact row
+			sql = "INSERT INTO contacts"
 				+ " (contactId, lastBundleReceived)"
 				+ " VALUES (?, ?)";
 			ps = txn.prepareStatement(sql);
@@ -375,6 +389,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			int rowsAffected = ps.executeUpdate();
 			assert rowsAffected == 1;
 			ps.close();
+			// Create a dummy received bundle row for BundleId.NONE
 			sql = "INSERT INTO receivedBundles"
 				+ " (bundleId, contactId, timestamp)"
 				+ " VALUES (?, ?, ?)";
@@ -385,6 +400,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 			rowsAffected = ps.executeUpdate();
 			assert rowsAffected == 1;
 			ps.close();
+			return c;
 		} catch(SQLException e) {
 			tryToClose(ps);
 			tryToClose(txn);
diff --git a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
index a61cfaa6cb76d65a4c769fc3cfc1842848f82a92..eb984b59aac4be2a58367a27ca0f9ebe9ef731af 100644
--- a/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
+++ b/components/net/sf/briar/db/ReadWriteLockDatabaseComponent.java
@@ -83,16 +83,19 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public void addContact(ContactId c) throws DbException {
-		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact " + c);
+	public ContactId addContact() throws DbException {
+		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
 		contactLock.writeLock().lock();
 		try {
 			messageStatusLock.writeLock().lock();
 			try {
 				Txn txn = db.startTransaction();
 				try {
-					db.addContact(txn, c);
+					ContactId c = db.addContact(txn);
 					db.commitTransaction(txn);
+					if(LOG.isLoggable(Level.FINE))
+						LOG.fine("Added contact " + c);
+					return c;
 				} catch(DbException e) {
 					db.abortTransaction(txn);
 					throw e;
@@ -307,6 +310,28 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public Set<ContactId> getContacts() throws DbException {
+		contactLock.readLock().lock();
+		try {
+			messageStatusLock.readLock().lock();
+			try {
+				Txn txn = db.startTransaction();
+				try {
+					Set<ContactId> contacts = db.getContacts(txn);
+					db.commitTransaction(txn);
+					return contacts;
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			} finally {
+				messageStatusLock.readLock().unlock();
+			}
+		} finally {
+			contactLock.readLock().unlock();
+		}
+	}
+
 	public Rating getRating(AuthorId a) throws DbException {
 		ratingLock.readLock().lock();
 		try {
@@ -329,8 +354,7 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		try {
 			Txn txn = db.startTransaction();
 			try {
-				HashSet<GroupId> subs = new HashSet<GroupId>();
-				for(GroupId g : db.getSubscriptions(txn)) subs.add(g);
+				Set<GroupId> subs = db.getSubscriptions(txn);
 				db.commitTransaction(txn);
 				return subs;
 			} catch(DbException e) {
diff --git a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
index bfe67f0a59d71aee0dc78901a38e112710526214..ba57834fa2e4a65074517c096e93e5af87d8fc56 100644
--- a/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
+++ b/components/net/sf/briar/db/SynchronizedDatabaseComponent.java
@@ -62,14 +62,17 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
-	public void addContact(ContactId c) throws DbException {
-		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact " + c);
+	public ContactId addContact() throws DbException {
+		if(LOG.isLoggable(Level.FINE)) LOG.fine("Adding contact");
 		synchronized(contactLock) {
 			synchronized(messageStatusLock) {
 				Txn txn = db.startTransaction();
 				try {
-					db.addContact(txn, c);
+					ContactId c = db.addContact(txn);
 					db.commitTransaction(txn);
+					if(LOG.isLoggable(Level.FINE))
+						LOG.fine("Added contact " + c);
+					return c;
 				} catch(DbException e) {
 					db.abortTransaction(txn);
 					throw e;
@@ -224,6 +227,22 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		}
 	}
 
+	public Set<ContactId> getContacts() throws DbException {
+		synchronized(contactLock) {
+			synchronized(messageStatusLock) {
+				Txn txn = db.startTransaction();
+				try {
+					Set<ContactId> contacts = db.getContacts(txn);
+					db.commitTransaction(txn);
+					return contacts;
+				} catch(DbException e) {
+					db.abortTransaction(txn);
+					throw e;
+				}
+			}
+		}
+	}
+
 	public Rating getRating(AuthorId a) throws DbException {
 		synchronized(ratingLock) {
 			Txn txn = db.startTransaction();
@@ -242,8 +261,7 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
 		synchronized(subscriptionLock) {
 			Txn txn = db.startTransaction();
 			try {
-				HashSet<GroupId> subs = new HashSet<GroupId>();
-				for(GroupId g : db.getSubscriptions(txn)) subs.add(g);
+				Set<GroupId> subs = db.getSubscriptions(txn);
 				db.commitTransaction(txn);
 				return subs;
 			} catch(DbException e) {
diff --git a/test/net/sf/briar/db/DatabaseComponentTest.java b/test/net/sf/briar/db/DatabaseComponentTest.java
index a432ab88e3901d28eccd1354e0821151765c2f52..dbab966d3f0013cd023fd57a6f0e433ad2a51561 100644
--- a/test/net/sf/briar/db/DatabaseComponentTest.java
+++ b/test/net/sf/briar/db/DatabaseComponentTest.java
@@ -83,7 +83,11 @@ public abstract class DatabaseComponentTest extends TestCase {
 			oneOf(database).getRating(txn, authorId);
 			will(returnValue(Rating.UNRATED));
 			// addContact(contactId)
-			oneOf(database).addContact(txn, contactId);
+			oneOf(database).addContact(txn);
+			will(returnValue(contactId));
+			// getContacts()
+			oneOf(database).getContacts(txn);
+			will(returnValue(Collections.singleton(contactId)));
 			// subscribe(groupId)
 			oneOf(database).addSubscription(txn, groupId);
 			// getSubscriptions()
@@ -102,7 +106,8 @@ public abstract class DatabaseComponentTest extends TestCase {
 
 		db.open(false);
 		assertEquals(Rating.UNRATED, db.getRating(authorId));
-		db.addContact(contactId);
+		assertEquals(contactId, db.addContact());
+		assertEquals(Collections.singleton(contactId), db.getContacts());
 		db.subscribe(groupId);
 		assertEquals(Collections.singleton(groupId), db.getSubscriptions());
 		db.unsubscribe(groupId);
diff --git a/test/net/sf/briar/db/H2DatabaseTest.java b/test/net/sf/briar/db/H2DatabaseTest.java
index 3c18e2314bbf37a756298d86f3864d4c93199118..7827da8524f1964b883ab48e38e542d91ff68a68 100644
--- a/test/net/sf/briar/db/H2DatabaseTest.java
+++ b/test/net/sf/briar/db/H2DatabaseTest.java
@@ -55,7 +55,7 @@ public class H2DatabaseTest extends TestCase {
 		super();
 		authorId = new AuthorId(TestUtils.getRandomId());
 		batchId = new BatchId(TestUtils.getRandomId());
-		contactId = new ContactId(123);
+		contactId = new ContactId(1);
 		groupId = new GroupId(TestUtils.getRandomId());
 		messageId = new MessageId(TestUtils.getRandomId());
 		timestamp = System.currentTimeMillis();
@@ -80,7 +80,7 @@ public class H2DatabaseTest extends TestCase {
 		// Store some records
 		Connection txn = db.startTransaction();
 		assertFalse(db.containsContact(txn, contactId));
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		assertTrue(db.containsContact(txn, contactId));
 		assertFalse(db.containsSubscription(txn, groupId));
 		db.addSubscription(txn, groupId);
@@ -124,6 +124,39 @@ public class H2DatabaseTest extends TestCase {
 		db.close();
 	}
 
+	@Test
+	public void testContactIdsIncrease() throws DbException {
+		ContactId contactId1 = new ContactId(2);
+		ContactId contactId2 = new ContactId(3);
+		ContactId contactId3 = new ContactId(4);
+		MessageFactory messageFactory = new TestMessageFactory();
+
+		// Create a new database
+		Database<Connection> db = open(false, messageFactory);
+		// Create three contacts
+		Connection txn = db.startTransaction();
+		assertFalse(db.containsContact(txn, contactId));
+		assertEquals(contactId, db.addContact(txn));
+		assertTrue(db.containsContact(txn, contactId));
+		assertFalse(db.containsContact(txn, contactId1));
+		assertEquals(contactId1, db.addContact(txn));
+		assertTrue(db.containsContact(txn, contactId1));
+		assertFalse(db.containsContact(txn, contactId2));
+		assertEquals(contactId2, db.addContact(txn));
+		assertTrue(db.containsContact(txn, contactId2));
+		/*
+		// Delete one of the contacts
+		db.removeContact(txn, contactId1);
+		assertFalse(db.containsContact(txn, contactId1));
+		*/
+		// Add another contact - a new ID should be created
+		assertFalse(db.containsContact(txn, contactId3));
+		assertEquals(contactId3, db.addContact(txn));
+		assertTrue(db.containsContact(txn, contactId3));
+		db.commitTransaction(txn);
+		db.close();
+	}
+
 	@Test
 	public void testRatings() throws DbException {
 		Mockery context = new Mockery();
@@ -176,7 +209,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -219,7 +252,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -268,7 +301,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, groupId);
 		db.addMessage(txn, message);
 		db.setSendability(txn, messageId, 1);
@@ -310,7 +343,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -346,7 +379,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact and some batches to ack
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addBatchToAck(txn, contactId, batchId);
 		db.addBatchToAck(txn, contactId, batchId1);
 		db.commitTransaction(txn);
@@ -378,7 +411,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -422,7 +455,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact, subscribe to a group and store a message
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		db.addSubscription(txn, groupId);
 		db.addSubscription(txn, contactId, groupId);
 		db.addMessage(txn, message);
@@ -476,7 +509,7 @@ public class H2DatabaseTest extends TestCase {
 		Database<Connection> db = open(false, messageFactory);
 		// Add a contact
 		Connection txn = db.startTransaction();
-		db.addContact(txn, contactId);
+		assertEquals(contactId, db.addContact(txn));
 		// Add an oustanding batch (associated with BundleId.NONE)
 		db.addOutstandingBatch(txn, contactId, batchId, empty);
 		// Receive a bundle