diff --git a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
index 1cda603b1163b01eafff8b14341b84f839b0ae28..c48e950162021f807c434570586f2ae533fa57e6 100644
--- a/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
+++ b/bin/dbus/cx.ring.Ring.ConfigurationManager.xml
@@ -1251,6 +1251,60 @@
            </arg>
        </method>
 
+       <method name="addContact" tp:name-for-bindings="addContact">
+           <tp:added version="3.0.0"/>
+           <arg type="s" name="accountID" direction="in">
+           </arg>
+           <arg type="s" name="uri" direction="in">
+           </arg>
+       </method>
+
+       <method name="removeContact" tp:name-for-bindings="removeContact">
+           <tp:added version="3.0.0"/>
+           <arg type="s" name="accountID" direction="in">
+           </arg>
+           <arg type="s" name="uri" direction="in">
+           </arg>
+       </method>
+
+       <method name="getContacts" tp:name-for-bindings="getContacts">
+           <tp:added version="3.0.0"/>
+           <arg type="s" name="accountID" direction="in">
+           </arg>
+           <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="VectorMapStringString"/>
+           <arg type="aa{ss}" name="contacts" direction="out" >
+             <tp:docstring>
+                 The list of contact IDs.
+             </tp:docstring>
+           </arg>
+       </method>
+
+       <signal name="contactAdded" tp:name-for-bindings="contactAdded">
+           <tp:added version="2.2.0"/>
+           <tp:docstring>
+               Notify clients that a new contact has been added.
+           </tp:docstring>
+           <arg type="s" name="accountID">
+           </arg>
+           <arg type="s" name="uri">
+           </arg>
+           <arg type="b" name="confirmed">
+           </arg>
+       </signal>
+
+       <signal name="contactRemoved" tp:name-for-bindings="contactRemoved">
+           <tp:added version="2.2.0"/>
+           <tp:docstring>
+               Notify clients that a new contact has been removed.
+           </tp:docstring>
+           <arg type="s" name="accountID">
+           </arg>
+           <arg type="s" name="uri">
+           </arg>
+           <arg type="b" name="banned">
+           </arg>
+       </signal>
+
        <method name="getAddrFromInterfaceName" tp:name-for-bindings="getAddrFromInterfaceName">
            <arg type="s" name="interface" direction="in">
            </arg>
diff --git a/bin/dbus/dbusclient.cpp b/bin/dbus/dbusclient.cpp
index 6c5853a7caa4c196bbc9afbf39428a36d563a59e..5dca85827ad27ec991251b90731daf1b8f2def81 100644
--- a/bin/dbus/dbusclient.cpp
+++ b/bin/dbus/dbusclient.cpp
@@ -176,6 +176,8 @@ DBusClient::initLibrary(int flags)
         exportable_callback<ConfigurationSignal::IncomingAccountMessage>(bind(&DBusConfigurationManager::incomingAccountMessage, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::AccountMessageStatusChanged>(bind(&DBusConfigurationManager::accountMessageStatusChanged, confM, _1, _2, _3, _4 )),
         exportable_callback<ConfigurationSignal::IncomingTrustRequest>(bind(&DBusConfigurationManager::incomingTrustRequest, confM, _1, _2, _3, _4 )),
+        exportable_callback<ConfigurationSignal::ContactAdded>(bind(&DBusConfigurationManager::contactAdded, confM, _1, _2, _3 )),
+        exportable_callback<ConfigurationSignal::ContactRemoved>(bind(&DBusConfigurationManager::contactRemoved, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::ExportOnRingEnded>(bind(&DBusConfigurationManager::exportOnRingEnded, confM, _1, _2, _3 )),
         exportable_callback<ConfigurationSignal::KnownDevicesChanged>(bind(&DBusConfigurationManager::knownDevicesChanged, confM, _1, _2 )),
         exportable_callback<ConfigurationSignal::NameRegistrationEnded>(bind(&DBusConfigurationManager::nameRegistrationEnded, confM, _1, _2, _3 )),
diff --git a/bin/dbus/dbusconfigurationmanager.cpp b/bin/dbus/dbusconfigurationmanager.cpp
index a782e901724f07ffc832e7d6af237b4a5093d4f7..7f65e35c8a2946e0483bfd319af9e7f471130770 100644
--- a/bin/dbus/dbusconfigurationmanager.cpp
+++ b/bin/dbus/dbusconfigurationmanager.cpp
@@ -511,6 +511,24 @@ DBusConfigurationManager::sendTrustRequest(const std::string& accountId, const s
     DRing::sendTrustRequest(accountId, to, payload);
 }
 
+void
+DBusConfigurationManager::addContact(const std::string& accountId, const std::string& uri)
+{
+    DRing::addContact(accountId, uri);
+}
+
+void
+DBusConfigurationManager::removeContact(const std::string& accountId, const std::string& uri)
+{
+    DRing::removeContact(accountId, uri);
+}
+
+auto
+DBusConfigurationManager::getContacts(const std::string& accountId) -> decltype(DRing::getContacts(accountId))
+{
+    return DRing::getContacts(accountId);
+}
+
 auto
 DBusConfigurationManager::getCredentials(const std::string& accountID) -> decltype(DRing::getCredentials(accountID))
 {
diff --git a/bin/dbus/dbusconfigurationmanager.h b/bin/dbus/dbusconfigurationmanager.h
index 7b6573c7f52ad2b5b45166bd791f42843dc5d799..17b02b4afa1b764c4bae617fec729abca812e306 100644
--- a/bin/dbus/dbusconfigurationmanager.h
+++ b/bin/dbus/dbusconfigurationmanager.h
@@ -143,6 +143,9 @@ class DBusConfigurationManager :
         bool acceptTrustRequest(const std::string& accountId, const std::string& from);
         bool discardTrustRequest(const std::string& accountId, const std::string& from);
         void sendTrustRequest(const std::string& accountId, const std::string& to, const std::vector<uint8_t>& payload);
+        void addContact(const std::string& accountId, const std::string& uri);
+        void removeContact(const std::string& accountId, const std::string& uri);
+        std::vector<std::map<std::string, std::string>> getContacts(const std::string& accountId);
         int exportAccounts(const std::vector<std::string>& accountIDs, const std::string& filepath, const std::string& password);
         int importAccounts(const std::string& archivePath, const std::string& password);
         void connectivityChanged();
diff --git a/src/client/configurationmanager.cpp b/src/client/configurationmanager.cpp
index 36324bf8a8c1ff1f66d7e290480441eeca3f000f..8de368ffafd137f13966d8d6783709e55af3c885 100644
--- a/src/client/configurationmanager.cpp
+++ b/src/client/configurationmanager.cpp
@@ -304,6 +304,28 @@ getKnownRingDevices(const std::string& accountId)
     return {};
 }
 
+/* contacts */
+
+void addContact(const std::string& accountId, const std::string& uri)
+{
+    if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId))
+        return acc->addContact(uri);
+}
+
+void removeContact(const std::string& accountId, const std::string& uri)
+{
+    if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId))
+        return acc->removeContact(uri);
+}
+
+std::vector<std::map<std::string, std::string>>
+getContacts(const std::string& accountId)
+{
+    if (auto acc = ring::Manager::instance().getAccount<ring::RingAccount>(accountId))
+        return acc->getContacts();
+    return {};
+}
+
 /* contact requests */
 std::map<std::string, std::string>
 getTrustRequests(const std::string& accountId)
diff --git a/src/client/ring_signal.cpp b/src/client/ring_signal.cpp
index 77f5b65ac7d58046340d57f28fac0cb895ace666..5cbbaecbfea97d99fa0e5e16fdb026c088fdcb9a 100644
--- a/src/client/ring_signal.cpp
+++ b/src/client/ring_signal.cpp
@@ -63,6 +63,8 @@ getSignalHandlers()
         exported_callback<DRing::ConfigurationSignal::IncomingAccountMessage>(),
         exported_callback<DRing::ConfigurationSignal::AccountMessageStatusChanged>(),
         exported_callback<DRing::ConfigurationSignal::IncomingTrustRequest>(),
+        exported_callback<DRing::ConfigurationSignal::ContactAdded>(),
+        exported_callback<DRing::ConfigurationSignal::ContactRemoved>(),
         exported_callback<DRing::ConfigurationSignal::ExportOnRingEnded>(),
         exported_callback<DRing::ConfigurationSignal::KnownDevicesChanged>(),
         exported_callback<DRing::ConfigurationSignal::NameRegistrationEnded>(),
diff --git a/src/dring/configurationmanager_interface.h b/src/dring/configurationmanager_interface.h
index 02db6f7403c474d2241a3c6a2bffc7fa6befc7aa..502ddb0aea31b37d4a6a6f5d5500d102988b53b6 100644
--- a/src/dring/configurationmanager_interface.h
+++ b/src/dring/configurationmanager_interface.h
@@ -156,9 +156,14 @@ std::vector<std::string> getCertificatesByStatus(const std::string& account, con
 std::map<std::string, std::string> getTrustRequests(const std::string& accountId);
 bool acceptTrustRequest(const std::string& accountId, const std::string& from);
 bool discardTrustRequest(const std::string& accountId, const std::string& from);
-
 void sendTrustRequest(const std::string& accountId, const std::string& to, const std::vector<uint8_t>& payload = {});
 
+/* Contacts */
+
+void addContact(const std::string& accountId, const std::string& uri);
+void removeContact(const std::string& accountId, const std::string& uri);
+std::vector<std::map<std::string, std::string>> getContacts(const std::string& accountId);
+
 /*
  * Import/Export accounts
  */
@@ -217,6 +222,14 @@ struct ConfigurationSignal {
                 constexpr static const char* name = "IncomingTrustRequest";
                 using cb_type = void(const std::string& /*account_id*/, const std::string& /*from*/, const std::vector<uint8_t>& payload, time_t received);
         };
+        struct ContactAdded {
+                constexpr static const char* name = "ContactAdded";
+                using cb_type = void(const std::string& /*account_id*/, const std::string& /*uri*/, bool confirmed);
+        };
+        struct ContactRemoved {
+                constexpr static const char* name = "ContactRemoved";
+                using cb_type = void(const std::string& /*account_id*/, const std::string& /*uri*/, bool banned);
+        };
         struct ExportOnRingEnded {
                 constexpr static const char* name = "ExportOnRingEnded";
                 using cb_type = void(const std::string& /*account_id*/, int state, const std::string& pin);
diff --git a/src/ringdht/ringaccount.cpp b/src/ringdht/ringaccount.cpp
index 0413efc40ae43bd9936132a4a1db1f5961f59406..514ad760c5c3d7d890767ae5b0a509e805f77fdc 100644
--- a/src/ringdht/ringaccount.cpp
+++ b/src/ringdht/ringaccount.cpp
@@ -131,6 +131,24 @@ RingAccount::TrustRequest {
         : from_device(sr.device), received(system_clock::from_time_t(sr.received)), payload(std::move(sr.payload)) {}
 };
 
+struct RingAccount::Contact
+{
+    //dht::InfoHash uri;
+
+    /** Time of contact addition */
+    time_t added {0};
+
+    /** Time of contact removal */
+    time_t removed {0};
+
+    /** True if we got confirmation that this contact also added us */
+    bool confirmed {false};
+
+    bool isActive() const { return added > removed; }
+
+    MSGPACK_DEFINE_MAP(added, removed, confirmed)
+};
+
 /**
  * Represents a known device attached to this Ring account
  */
@@ -195,9 +213,8 @@ struct RingAccount::DeviceSync : public dht::EncryptedValue<DeviceSync>
     uint64_t date;
     std::string device_name;
     std::map<dht::InfoHash, std::string> devices_known;
-    std::set<dht::InfoHash> peers_trusted;
-    std::set<dht::InfoHash> peers_banned;
-    MSGPACK_DEFINE_MAP(date, device_name, devices_known, peers_trusted, peers_banned)
+    std::map<dht::InfoHash, Contact> peers;
+    MSGPACK_DEFINE_MAP(date, device_name, devices_known, peers)
 };
 
 static constexpr int ICE_COMPONENTS {1};
@@ -829,7 +846,7 @@ RingAccount::hasSignedReceipt()
 dht::crypto::Identity
 RingAccount::loadIdentity()
 {
-    RING_WARN("loadIdentity() %s %s", tlsCertificateFile_.c_str(), tlsPrivateKeyFile_.c_str());
+    RING_DBG("loadIdentity() %s %s", tlsCertificateFile_.c_str(), tlsPrivateKeyFile_.c_str());
     dht::crypto::Certificate dht_cert;
     dht::crypto::PrivateKey dht_key;
     try {
@@ -1085,8 +1102,9 @@ RingAccount::revokeDevice(const std::string& password, const std::string& device
     auto fa = ThreadPool::instance().getShared<ArchiveContent>(
                     std::bind(&RingAccount::readArchive, this, password)
                 );
+    auto sthis = shared();
     findCertificate(dht::InfoHash(device),
-                    [fa,sthis=shared(),password](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable
+                    [fa,sthis,password](const std::shared_ptr<dht::crypto::Certificate>& crt) mutable
     {
         sthis->foundAccountDevice(crt);
         auto a = fa->get();
@@ -1339,10 +1357,11 @@ RingAccount::loadAccount(const std::string& archive_password, const std::string&
     if (registrationState_ == RegistrationState::INITIALIZING)
         return;
 
-    RING_WARN("RingAccount::loadAccount");
+    RING_DBG("RingAccount::loadAccount");
     try {
         loadIdentity();
         loadKnownDevices();
+        loadContacts();
         loadTrustRequests();
 
         bool hasArchive = not archivePath_.empty() and fileutils::isFile(archivePath_);
@@ -2024,23 +2043,34 @@ RingAccount::doRegister_()
                     }
 
                     RING_WARN("Got trust request from: %s / %s", peer_account.toString().c_str(), v.from.toString().c_str());
-                    auto req = this_.trustRequests_.find(peer_account);
-                    if (req == this_.trustRequests_.end()) {
-                        req = this_.trustRequests_.emplace(peer_account, TrustRequest{
-                            v.from, std::chrono::system_clock::now(), std::move(v.payload)
-                        }).first;
+
+                    // Check existing contact
+                    auto contact = this_.contacts_.find(peer_account);
+                    if (contact != this_.contacts_.end()) {
+                        // Contact exists, update confirmation status
+                        contact->second.confirmed = true;
+                        emitSignal<DRing::ConfigurationSignal::ContactAdded>(this_.getAccountID(), peer_account.toString(), true);
+                        this_.syncDevices();
                     } else {
-                        req->second.from_device = v.from;
-                        req->second.received = std::chrono::system_clock::now();
-                        req->second.payload = std::move(v.payload);
+                        // Add trust request
+                        auto req = this_.trustRequests_.find(peer_account);
+                        if (req == this_.trustRequests_.end()) {
+                            req = this_.trustRequests_.emplace(peer_account, TrustRequest{
+                                v.from, std::chrono::system_clock::now(), std::move(v.payload)
+                            }).first;
+                        } else {
+                            req->second.from_device = v.from;
+                            req->second.received = std::chrono::system_clock::now();
+                            req->second.payload = std::move(v.payload);
+                        }
+                        this_.saveTrustRequests();
+                        emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(
+                            this_.getAccountID(),
+                            req->first.toString(),
+                            req->second.payload,
+                            std::chrono::system_clock::to_time_t(req->second.received)
+                        );
                     }
-                    this_.saveTrustRequests();
-                    emitSignal<DRing::ConfigurationSignal::IncomingTrustRequest>(
-                        this_.getAccountID(),
-                        req->first.toString(),
-                        req->second.payload,
-                        std::chrono::system_clock::to_time_t(req->second.received)
-                    );
                 });
                 return true;
             }
@@ -2225,6 +2255,8 @@ RingAccount::foundPeerDevice(const std::shared_ptr<dht::crypto::Certificate>& cr
         return false;
     }
 
+    RING_WARN("foundPeerDevice dev:%s CA:%s", crt->getId().toString().c_str(), top_issuer->getId().toString().c_str());
+
     // Check peer certificate chain
     // Trust store with top issuer as the only CA
     tls::TrustStore peer_trust;
@@ -2691,7 +2723,101 @@ RingAccount::getContactHeader(pjsip_transport* t)
     return contact_;
 }
 
+/* contacts */
+
+void
+RingAccount::addContact(const std::string& uri)
+{
+    dht::InfoHash h (uri);
+    auto c = contacts_.find(h);
+    if (c == contacts_.end())
+        c = contacts_.emplace(h, Contact{}).first;
+    c->second.added = std::time(nullptr);
+    trust_.setCertificateStatus(uri, tls::TrustStore::PermissionStatus::ALLOWED);
+    saveContacts();
+    emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), uri, c->second.confirmed);
+    syncDevices();
+}
+
+void
+RingAccount::removeContact(const std::string& uri)
+{
+    dht::InfoHash h (uri);
+    auto c = contacts_.find(h);
+    if (c != contacts_.end()) {
+        c->second.removed = std::time(nullptr);
+        trust_.setCertificateStatus(uri, tls::TrustStore::PermissionStatus::BANNED);
+        saveContacts();
+        emitSignal<DRing::ConfigurationSignal::ContactRemoved>(getAccountID(), uri, c->second.confirmed);
+        syncDevices();
+    }
+}
+
+std::vector<std::map<std::string, std::string>>
+RingAccount::getContacts() const
+{
+    std::vector<std::map<std::string, std::string>> ret;
+    ret.reserve(contacts_.size());
+    for (const auto& c : contacts_) {
+        std::map<std::string, std::string> cm {
+            {"id", c.first.toString()},
+            {"added", std::to_string(c.second.added)}
+        };
+        if (c.second.isActive())
+            cm.emplace("confirmed", c.second.confirmed ? TRUE_STR : FALSE_STR);
+        else
+            cm.emplace("banned", TRUE_STR);
+        ret.emplace_back(std::move(cm));
+    }
+    return ret;
+}
+
+void
+RingAccount::updateContact(const dht::InfoHash& id, const Contact& contact)
+{
+    auto c = contacts_.find(id);
+    if (c == contacts_.end()) {
+        c = contacts_.emplace(id, contact).first;
+        emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), id.toString(), c->second.confirmed);
+    } else {
+        c->second.added = std::max(contact.added, c->second.added);
+        c->second.removed = std::max(contact.removed, c->second.removed);
+        if (contact.confirmed != c->second.confirmed) {
+            c->second.confirmed = std::max(contact.confirmed, c->second.confirmed);
+            emitSignal<DRing::ConfigurationSignal::ContactAdded>(getAccountID(), id.toString(), c->second.confirmed);
+        }
+    }
+    trust_.setCertificateStatus(id.toString(), c->second.isActive() ? tls::TrustStore::PermissionStatus::ALLOWED : tls::TrustStore::PermissionStatus::BANNED);
+}
+
+void
+RingAccount::loadContacts()
+{
+    decltype(contacts_) contacts;
+    try {
+        // read file
+        auto file = fileutils::loadFile(idPath_+DIR_SEPARATOR_STR "contacts");
+        // load values
+        msgpack::object_handle oh = msgpack::unpack((const char*)file.data(), file.size());
+        oh.get().convert(contacts);
+    } catch (const std::exception& e) {
+        RING_WARN("Error loading contacts: %s", e.what());
+        return;
+    }
+
+    for (auto& peer : contacts)
+        updateContact(peer.first, peer.second);
+}
+
+void
+RingAccount::saveContacts() const
+{
+    std::ofstream file(idPath_+DIR_SEPARATOR_STR "contacts", std::ios::trunc);
+    msgpack::pack(file, contacts_);
+}
+
 /* trust requests */
+
 std::map<std::string, std::string>
 RingAccount::getTrustRequests() const
 {
@@ -2707,7 +2833,7 @@ RingAccount::acceptTrustRequest(const std::string& from)
     dht::InfoHash f(from);
     auto i = trustRequests_.find(f);
     if (i != trustRequests_.end()) {
-        trust_.setCertificateStatus(from, tls::TrustStore::PermissionStatus::ALLOWED);
+        addContact(from);
         trustRequests_.erase(i);
         saveTrustRequests();
         return true;
@@ -2729,7 +2855,7 @@ RingAccount::discardTrustRequest(const std::string& from)
 void
 RingAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload)
 {
-    setCertificateStatus(to, tls::TrustStore::PermissionStatus::ALLOWED);
+    addContact(to);
     auto toH = dht::InfoHash(to);
     forEachDevice(toH, [toH,payload](const std::shared_ptr<RingAccount>& shared, const dht::InfoHash& dev)
     {
@@ -2741,7 +2867,7 @@ RingAccount::sendTrustRequest(const std::string& to, const std::vector<uint8_t>&
 }
 
 void
-RingAccount::saveTrustRequests()
+RingAccount::saveTrustRequests() const
 {
     std::ofstream file(idPath_+DIR_SEPARATOR_STR "incomingTrustRequests", std::ios::trunc);
 
@@ -2779,12 +2905,15 @@ RingAccount::loadTrustRequests()
     }
 }
 
+/* sync */
+
 void
 RingAccount::syncDevices()
 {
     DeviceSync sync_data;
     sync_data.date = std::chrono::system_clock::now().time_since_epoch().count();
     sync_data.device_name = ringDeviceName_;
+    sync_data.peers = contacts_;
     RING_WARN("Building device sync for %s %s", ringDeviceName_.c_str(), ringDeviceId_.c_str());
     for (const auto& dev : knownDevices_) {
         if (dev.first.toString() == ringDeviceId_)
@@ -2816,7 +2945,9 @@ RingAccount::onReceiveDeviceSync(DeviceSync&& sync)
         RING_DBG("onReceiveDeviceSync: dropping outdated sync data");
         return;
     }
-    RING_WARN("Received device sync data %zu", sync.devices_known.size());
+
+    // Sync known devices
+    RING_WARN("Received device sync data %lu", sync.devices_known.size());
     for (const auto& d : sync.devices_known) {
         auto shared = std::static_pointer_cast<RingAccount>(shared_from_this());
         findCertificate(d.first, [shared,d](const std::shared_ptr<dht::crypto::Certificate>& crt) {
@@ -2825,8 +2956,14 @@ RingAccount::onReceiveDeviceSync(DeviceSync&& sync)
             shared->foundAccountDevice(crt, d.second);
         });
     }
-    it->second.last_sync = sync_date;
     saveKnownDevices();
+
+    // Sync contacts
+    for (const auto& peer : sync.peers)
+        updateContact(peer.first, peer.second);
+    saveContacts();
+
+    it->second.last_sync = sync_date;
 }
 
 void
diff --git a/src/ringdht/ringaccount.h b/src/ringdht/ringaccount.h
index 768faea65ee8b42db91619186950e2854b0b41d6..86edc17f5e8c60547d07986976831eb515b4db6f 100644
--- a/src/ringdht/ringaccount.h
+++ b/src/ringdht/ringaccount.h
@@ -290,6 +290,10 @@ class RingAccount : public SIPAccountBase {
         bool acceptTrustRequest(const std::string& from);
         bool discardTrustRequest(const std::string& from);
 
+        void addContact(const std::string& uri);
+        void removeContact(const std::string& uri);
+        std::vector<std::map<std::string, std::string>> getContacts() const;
+
         void sendTrustRequest(const std::string& to, const std::vector<uint8_t>& payload);
         virtual void sendTextMessage(const std::string& to, const std::map<std::string, std::string>& payloads, uint64_t id) override;
 
@@ -325,6 +329,7 @@ class RingAccount : public SIPAccountBase {
         struct DeviceAnnouncement;
         struct DeviceSync;
         struct BuddyInfo;
+        struct Contact;
 
         void syncDevices();
         void onReceiveDeviceSync(DeviceSync&& sync);
@@ -449,7 +454,12 @@ class RingAccount : public SIPAccountBase {
 
         std::map<dht::InfoHash, TrustRequest> trustRequests_;
         void loadTrustRequests();
-        void saveTrustRequests();
+        void saveTrustRequests() const;
+
+        std::map<dht::InfoHash, Contact> contacts_;
+        void loadContacts();
+        void saveContacts() const;
+        void updateContact(const dht::InfoHash&, const Contact&);
 
         tls::TrustStore trust_;