diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index c0abb99ca994d4d9ff91947fe00f5dbf0dbeaeee..8a572172298812028952bbdab096defa03015dbc 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -60,6 +60,7 @@ set(VEREIGNLIB_SRC
   vereign/grpc/service_registry.cc
   vereign/grpc/server.cc
 
+  vereign/sqlite/statement.cc
   vereign/sqlite/connection.cc
 
   vereign/bytes/view_dump.cc
@@ -75,6 +76,7 @@ set(VEREIGNLIB_SRC
   vereign/crypto/digest.cc
 
 
+  vereign/kvstore/lock.cc
   vereign/kvstore/sqlite_storage.cc
   vereign/kvstore/crypto_storage.cc
 
diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc
index e8288a68446d589959df01778774b5344b9429fa..cdf8de983f263a51b356e9ea05961554cb6db480 100644
--- a/cpp/src/csandbox.cc
+++ b/cpp/src/csandbox.cc
@@ -12,39 +12,5 @@ auto main(int argc, char** argv) -> int {
   boost::ignore_unused(argc);
   boost::ignore_unused(argv);
 
-  // fs::path ;
-
-  // std::wstring s = L"c:\\проба.txt";
-  std::string path = "c:\\ztrash\\проба.txt";
-  std::wstring ws = vereign::string::widen(path);
-
-  std::string p2 = vereign::string::narrow(ws);
-  std::cout << vereign::bytes::dump(vereign::bytes::View(path)) << std::endl;
-  std::cout << vereign::bytes::dump(vereign::bytes::View(p2)) << std::endl;
-  if (p2 == path) {
-    std::cout << "yes" << std::endl;
-  }
-
-  std::cout << vereign::bytes::dump(vereign::bytes::View(ws)) << std::endl;
-
-  // std::ofstream f{ws};
-  // std::wcout << ws;
-  std::cout << path << std::endl;
-  std::wcout << ws << std::endl;
-  // std::cout << "проба" << std::endl;
-  // wchar_t ru[] = L"Привет"; //Russian language
-  // std::wcout << ru;
-  // std::cout << std::endl;
-  // std::cout << "Temp directory is " << fs::temp_directory_path() << '\n';
-  // std::cout << "Home directory is " << fs::user << '\n';
-
-  // p.wstring();
-  // std::cout << p.string() << std::endl;;
-
-  // wchar_t* in = L"ทดสอบ"; // thai language
-  // char* out=(char *)malloc(15);
-  // WideCharToMultiByte(874, 0, in, 15, out, 15, NULL, NULL);
-  // printf(out); // result is correctly in Thai although not neat
-
-  return 1;
+  return 0;
 }
diff --git a/cpp/src/vereign/bytes/view.hh b/cpp/src/vereign/bytes/view.hh
index c64f59a14003857e8d7b5c3f7efbf0b619ad8f97..672b6229ada16d3b2b1a1351ddcc3865d30a2e3e 100644
--- a/cpp/src/vereign/bytes/view.hh
+++ b/cpp/src/vereign/bytes/view.hh
@@ -80,6 +80,10 @@ public:
     return std::memcmp(data_, other.data_, size_) == 0;
   }
 
+  auto operator!=(View other) const noexcept -> bool {
+    return !(*this == other);
+  }
+
   auto operator[](std::size_t index) const -> const uint8_t& {
     if (index >= size_ ) {
       throw std::runtime_error("index out of bounds");
diff --git a/cpp/src/vereign/grpc/server.hh b/cpp/src/vereign/grpc/server.hh
index 5ab7301ba1e614ad8f340948ab67d5508b613231..9b992d5a0974c8d48f9d150c867078052bd1b462 100644
--- a/cpp/src/vereign/grpc/server.hh
+++ b/cpp/src/vereign/grpc/server.hh
@@ -9,7 +9,7 @@ namespace vereign::grpc {
 /**
  * BindError is thrown when the Server::Server could not start listening.
  */
-class BindError: public virtual std::exception {
+class BindError: public std::exception {
 public:
   auto what() const noexcept -> const char* override {
     return "gRPC listen failed";
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
index 66ecd53e8e80c36fca05871db698fcbda057bc4e..b70efafb860153bc30415a7844f60a8a68b58a07 100644
--- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
@@ -2,24 +2,25 @@
 
 #include <vereign/kvstore/detail/value_encoder.hh>
 #include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
 
+#include <vereign/crypto/digest.hh>
+#include <vereign/crypto/errors.hh>
 #include <vereign/crypto/rand.hh>
 #include <vereign/crypto/rsa.hh>
 #include <vereign/crypto/aes.hh>
 #include <vereign/crypto/bio.hh>
 
-#include <openssl/base.h>
-#include <openssl/bn.h>
-#include <openssl/evp.h>
-#include <openssl/sha.h>
-#include <openssl/aes.h>
-#include <openssl/rsa.h>
-#include <openssl/pem.h>
+#include <chrono>
 
 namespace {
+  // FIXME: should these be injected and provided by the integrator
   constexpr int iterations = 1 << 18;
   constexpr int saltSizeBytes = 64;
   constexpr int aesKeySizeBytes = 32;
+
+  constexpr int lockRetryCount = 10;
+  constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
 }
 
 namespace vereign::kvstore::detail {
@@ -29,12 +30,14 @@ CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage)
 {}
 
 void CryptoStorageImpl::Open(const std::string& pin) {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
   bytes::Buffer salt;
-  auto iterations = storage_.GetInt64("master_key_iterations");
+  auto iterations = storage_.GetInt64("__master_key_iterations");
   if (iterations == 0) {
     throw StorageNotInitializedError{};
   }
-  storage_.GetBytes("master_key_salt", salt);
+  storage_.GetBytes("__master_key_salt", salt);
 
   bytes::Buffer key{aesKeySizeBytes};
 
@@ -54,6 +57,16 @@ void CryptoStorageImpl::Open(const std::string& pin) {
   key.IncSize(aesKeySizeBytes);
 
   key_ = std::move(key);
+
+  // FIXME: write tests for tampering and regular identity change
+  try {
+    bytes::Buffer tag;
+    GetBytes("__tag", tag);
+    tag_ = std::move(tag);
+
+  } catch (const crypto::Error& err) {
+    throw IdentityChanged{};
+  }
 }
 
 void CryptoStorageImpl::Reset(const std::string& pin) {
@@ -76,14 +89,25 @@ void CryptoStorageImpl::Reset(const std::string& pin) {
   }
 
   key.IncSize(aesKeySizeBytes);
+  key_ = std::move(key);
 
-  storage_.PutInt64("master_key_iterations", iterations);
-  storage_.PutBytes("master_key_salt", salt.View());
+  bytes::Buffer tag{saltSizeBytes};
+  crypto::Rand(tag);
 
-  key_ = std::move(key);
+  {
+    kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+    storage_.DeleteAll();
+
+    encryptBytes("__tag", tag.View());
+    tag_ = std::move(tag);
+
+    storage_.PutInt64("__master_key_iterations", iterations);
+    storage_.PutBytes("__master_key_salt", salt.View());
+  }
 }
 
-void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
+void CryptoStorageImpl::encryptBytes(const std::string& key, bytes::View value) {
   if (key_.Size() == 0) {
     throw Error("crypto storage is not initialized");
   }
@@ -100,7 +124,26 @@ void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
   storage_.PutBytes(key, encoded_value.View());
 }
 
+void CryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
+  bytes::Buffer tag;
+  try {
+    GetBytes("__tag", tag);
+  } catch (const crypto::Error& err) {
+    throw IdentityChanged{};
+  }
+
+  if (tag.View() != tag_.View()) {
+    throw IdentityChanged{};
+  }
+
+  encryptBytes(key, value);
+}
+
 void CryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& value) {
+  kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
+
   if (key_.Size() == 0) {
     throw Error("crypto storage is not initialized");
   }
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh
index 1a0cdc926400d2a3f235afa3180a4ecacce91064..327603621fb64ecbd9b05ccd3740ca3e5287933a 100644
--- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.hh
@@ -19,10 +19,15 @@ public:
   void PutBytes(const std::string& key, bytes::View value);
   void GetBytes(const std::string& key, bytes::Buffer& value);
 
+private:
+  void encryptBytes(const std::string& key, bytes::View value);
+
 private:
   kvstore::Storage& storage_;
 
   bytes::Buffer key_;
+
+  bytes::Buffer tag_;
 };
 
 } // namespace vereign::kvstore::detail
diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh
index 694b3c5d38c0a4b91c134ed56ae9b397d0c17a49..371f273b1257ba5a464f2d03737a8bb86536707e 100644
--- a/cpp/src/vereign/kvstore/errors.hh
+++ b/cpp/src/vereign/kvstore/errors.hh
@@ -21,6 +21,22 @@ public:
   }
 };
 
+class LockError : public Error {
+public:
+  LockError()
+    : Error{"cannot acquire storage lock"}
+  {
+  }
+};
+
+class IdentityChanged : public Error {
+public:
+  IdentityChanged()
+    : Error{"identity has been changed"}
+  {
+  }
+};
+
 } // namespace vereign::kvstore
 
 #endif // __VEREIGN_KVSTORE_ERRORS_HH
diff --git a/cpp/src/vereign/kvstore/lock.cc b/cpp/src/vereign/kvstore/lock.cc
new file mode 100644
index 0000000000000000000000000000000000000000..65935c0c3583dfcba3cb52bd11b2c204d4f48056
--- /dev/null
+++ b/cpp/src/vereign/kvstore/lock.cc
@@ -0,0 +1,39 @@
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/kvstore/errors.hh>
+
+#include <thread>
+#include <sqlite3.h>
+
+namespace vereign::kvstore {
+
+Lock::Lock(Storage& storage)
+  : storage_{storage}
+{
+  storage.Lock();
+}
+
+Lock::Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_interval)
+  : storage_{storage}
+{
+  for (int i = 0; i < retry_count; i++) {
+    try {
+      storage.Lock();
+
+      return;
+    } catch (const LockError& err) {
+      std::this_thread::sleep_for(sleep_interval);
+
+      continue;
+    }
+  }
+
+  throw LockError{};
+}
+
+Lock::~Lock() noexcept {
+  storage_.Unlock();
+}
+
+
+} // namespace vereign::kvstore
diff --git a/cpp/src/vereign/kvstore/lock.hh b/cpp/src/vereign/kvstore/lock.hh
new file mode 100644
index 0000000000000000000000000000000000000000..38d0d47da88f32e4d117c23530712080456192a6
--- /dev/null
+++ b/cpp/src/vereign/kvstore/lock.hh
@@ -0,0 +1,25 @@
+#ifndef __VEREIGN_KVSTORE_LOCK_HH
+#define __VEREIGN_KVSTORE_LOCK_HH
+
+#include <vereign/kvstore/storage.hh>
+#include <chrono>
+
+namespace vereign::kvstore {
+
+class Lock {
+public:
+  explicit Lock(Storage& storage);
+  Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_interval);
+  ~Lock() noexcept;
+
+  Lock(const Lock&) = delete;
+  auto operator=(const Lock&) -> Lock&  = delete;
+
+private:
+  Storage& storage_;
+};
+
+
+} // namespace vereign::kvstore
+
+#endif // __VEREIGN_KVSTORE_LOCK_HH
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc
index de5b48d28b5f2e68628f4c1b3b155de3f29f4db9..3e45d0f5f6bd74806b7ad2a1a5f49d9c9e765a5e 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.cc
+++ b/cpp/src/vereign/kvstore/sqlite_storage.cc
@@ -1,80 +1,81 @@
 #include <vereign/kvstore/sqlite_storage.hh>
 
-#include <vereign/encoding/binary.hh>
 #include <vereign/kvstore/errors.hh>
+#include <vereign/kvstore/lock.hh>
+
+#include <vereign/encoding/binary.hh>
 #include <boost/optional.hpp>
 #include <vereign/core/lock_guard.hh>
+#include <vereign/sqlite/errors.hh>
+#include <sqlite3.h>
 
-#include <mutex>
 #include <array>
 
+namespace {
+constexpr int createTableRetryCount = 10;
+constexpr auto createTableRetrySleep = std::chrono::milliseconds{1000};
+}
+
 namespace vereign::kvstore {
 
-namespace detail {
+SqliteStorage::SqliteStorage(const std::string& db_path)
+  : db_{db_path}
+{
+  kvstore::Lock l{*this, createTableRetryCount, createTableRetrySleep};
 
-class SqliteLock {
-public:
-  SqliteLock(sqlite::Connection& db)
-    : db_{db}
-  {
-  }
+  db_.Execute(R"(
+CREATE TABLE IF NOT EXISTS storage (
+  key TEXT PRIMARY KEY NOT NULL,
+  value BLOB
+);
+  )");
+}
 
-  ~SqliteLock() {
-    Unlock();
+SqliteStorage::~SqliteStorage() {
+  if (lock_count_ != 0) {
+    db_.Commit();
   }
+}
 
-  void Lock() {
-    if (!tr_) {
-      tr_ = db_.BeginExplicitTransaction();
-    }
+void SqliteStorage::Lock() {
+  if (lock_count_ != 0) {
+    lock_count_++;
 
-    count_++;
+    return;
   }
 
-  void Unlock() {
-    if (count_ == 0 || !tr_) {
-      throw Error{"SqliteStorage unexpected call Unlock with non existent lock"};
+  try {
+    db_.BeginExplicitTransaction();
+    lock_count_++;
+  } catch (const sqlite::Error& err) {
+    if (err.code() == SQLITE_BUSY) {
+      throw LockError{};
     }
 
-    count_--;
-    if (count_ == 0) {
-      tr_->Commit();
-      tr_.reset();
-    }
+    throw;
   }
-
-private:
-  int count_ = 0;
-  boost::optional<sqlite::Transaction> tr_;
-  sqlite::Connection& db_;
-};
-
 }
 
-SqliteStorage::SqliteStorage(const std::string& db_path)
-  : db_{db_path},
-    lock_{std::make_unique<detail::SqliteLock>(db_)}
-{
-  db_.Execute(R"(
-CREATE TABLE IF NOT EXISTS storage (
-  key TEXT PRIMARY KEY NOT NULL,
-  value BLOB
-);
-  )");
+void SqliteStorage::Unlock() {
+  if (lock_count_ == 0) {
+    throw Error{"unexpected call Unlock with non existent lock"};
+  }
+
+  lock_count_--;
+  if (lock_count_ == 0) {
+    db_.Commit();
+  }
 }
 
-SqliteStorage::~SqliteStorage() = default;
 
-void SqliteStorage::Lock() {
-  lock_->Lock();
-}
+void SqliteStorage::DeleteAll() {
+  kvstore::Lock l{*this};
 
-void SqliteStorage::Unlock() {
-  lock_->Unlock();
+  db_.Execute("DELETE FROM storage;");
 }
 
 void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
-  core::LockGuard<detail::SqliteLock> l{*lock_};
+  kvstore::Lock l{*this};
 
   auto stmt = db_.Prepare("REPLACE INTO storage(key, value) VALUES(?, ?);");
   stmt.BindText(1, key);
@@ -83,7 +84,7 @@ void SqliteStorage::PutBytes(const std::string& key, bytes::View value) {
 }
 
 void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
-  core::LockGuard<detail::SqliteLock> l{*lock_};
+  kvstore::Lock l{*this};
 
   auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
   stmt.BindText(1, key);
@@ -95,7 +96,7 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
 }
 
 void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
-  core::LockGuard<detail::SqliteLock> l{*lock_};
+  kvstore::Lock l{*this};
 
   std::array<uint8_t, 8> encoded;
   encoding::binary::EncodeUint64(encoded.data(), value);
@@ -107,7 +108,7 @@ void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
 }
 
 auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
-  core::LockGuard<detail::SqliteLock> l{*lock_};
+  kvstore::Lock l{*this};
 
   auto stmt = db_.Prepare("SELECT value FROM storage WHERE key = ?");
   stmt.BindText(1, key);
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.hh b/cpp/src/vereign/kvstore/sqlite_storage.hh
index 012c35f494faae64d4bfc04b9e23cfb19b6d8555..b07f39f49921b46d4fb7620f8624fac51ffcc5ef 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.hh
+++ b/cpp/src/vereign/kvstore/sqlite_storage.hh
@@ -4,14 +4,8 @@
 #include <vereign/kvstore/storage.hh>
 #include <vereign/sqlite/connection.hh>
 
-#include <memory>
-
 namespace vereign::kvstore {
 
-namespace detail {
-class SqliteLock;
-}
-
 class SqliteStorage : public Storage {
 public:
   SqliteStorage(const std::string& db_path);
@@ -23,6 +17,8 @@ public:
   void Lock() override;
   void Unlock() override;
 
+  void DeleteAll() override;
+
   void PutBytes(const std::string& key, bytes::View value) override;
   void GetBytes(const std::string& key, bytes::Buffer& value) override;
 
@@ -31,7 +27,7 @@ public:
 
 private:
   sqlite::Connection db_;
-  std::unique_ptr<detail::SqliteLock> lock_;
+  int lock_count_ = 0;
 };
 
 } // namespace vereign::kvstore
diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh
index 2bdb268d6395ceb981bed9adf6b442e7b34c0fe1..aa21c54684d0aef25313459f9b0f0194847beff1 100644
--- a/cpp/src/vereign/kvstore/storage.hh
+++ b/cpp/src/vereign/kvstore/storage.hh
@@ -10,6 +10,8 @@ public:
   virtual void Lock() = 0;
   virtual void Unlock() = 0;
 
+  virtual void DeleteAll() = 0;
+
   virtual void PutBytes(const std::string& key, bytes::View value) = 0;
   virtual void GetBytes(const std::string& key, bytes::Buffer& value) = 0;
 
diff --git a/cpp/src/vereign/sqlite/connection.cc b/cpp/src/vereign/sqlite/connection.cc
index e12fb7c4a85e97df396c12c2d8649290e05b1499..eefe6a7de2ff004c777f88deb81eb3cee64f9271 100644
--- a/cpp/src/vereign/sqlite/connection.cc
+++ b/cpp/src/vereign/sqlite/connection.cc
@@ -1,163 +1,56 @@
-#include <stdexcept>
 #include <vereign/sqlite/connection.hh>
-#include <vereign/sqlite/errors.hh>
 
+#include <vereign/sqlite/errors.hh>
 #include <vereign/core/scope_guard.hh>
 
 #include <fmt/format.h>
 #include <sqlite3.h>
-#include <memory>
+#include <iostream>
 
 namespace vereign::sqlite {
 
-Transaction::Transaction(sqlite3* db)
-  : finished_{false},
-    db_{db}
-{}
-
-Transaction::Transaction(Transaction&& other)
-  : finished_{other.finished_},
-    db_{other.db_}
+Connection::Connection(const std::string& path)
+  : db_{nullptr}
 {
-  other.finished_ = true;
-}
-
-auto Transaction::operator=(Transaction&& other) -> Transaction& {
-  if (!finished_) {
-    throw std::runtime_error{"cannot move assign to non finished transaction"};
+  auto rc = sqlite3_open(path.data(), &db_);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("open db failed, err: {}", sqlite3_errmsg(db_))};
   }
+}
 
-  db_ = other.db_;
-  finished_ = other.finished_;
-  other.finished_ = true;
-
-  return *this;
+Connection::~Connection() noexcept {
+  sqlite3_close(db_);
 }
 
-void Transaction::Commit() {
+void Connection::BeginExplicitTransaction() {
   char* errMsg = nullptr;
   auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
-
-  auto rc = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, &errMsg);
+  auto rc = sqlite3_exec(db_, "BEGIN EXCLUSIVE", nullptr, nullptr, &errMsg);
   if (rc != SQLITE_OK) {
-    throw Error(rc, fmt::format("commit transaction failed, err: {}", errMsg));
+    throw Error(rc, fmt::format("starting transaction failed, err: {}", errMsg));
   }
-
-  finished_ = true;
 }
 
-void Transaction::Rollback() {
+void Connection::Commit() {
   char* errMsg = nullptr;
   auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
 
-  auto rc = sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, &errMsg);
-  if (rc != SQLITE_OK) {
-    throw Error(rc, fmt::format("rollback transaction failed, err: {}", errMsg));
-  }
-
-  finished_ = true;
-}
-
-Transaction::~Transaction() {
-  if (!finished_) {
-    Rollback();
-  }
-}
-
-Statement::Statement(sqlite3* db, sqlite3_stmt* stmt)
-  : db_{db},
-    stmt_{stmt}
-{}
-
-void Statement::Finalize() {
-  sqlite3_finalize(stmt_);
-}
-
-Statement::~Statement() {
-  sqlite3_finalize(stmt_);
-}
-
-void Statement::BindBlob(int index, bytes::View blob) {
-  auto rc = sqlite3_bind_blob64(stmt_, index, blob.CharData(), blob.Size(), SQLITE_STATIC);
-  if (rc != SQLITE_OK) {
-    throw Error{rc, fmt::format("bind blob parameter failed, err: {}", sqlite3_errmsg(db_))};
-  }
-}
-
-void Statement::BindText(int index, const std::string& text) {
-  auto rc = sqlite3_bind_text(stmt_, index, text.data(), text.size(), SQLITE_STATIC);
-  if (rc != SQLITE_OK) {
-    throw Error{rc, fmt::format("bind text parameter failed, err: {}", sqlite3_errmsg(db_))};
-  }
-}
-
-auto Statement::Step() -> bool {
-  auto rc = sqlite3_step(stmt_);
-  switch (rc) {
-  case SQLITE_DONE:
-    return true;
-  case SQLITE_ROW:
-    return false;
-  default:
-    throw Error{rc, fmt::format("executing statement failed, err: {}", sqlite3_errmsg(db_))};
-  }
-}
-
-auto Statement::GetColumnBlob(int index) -> bytes::View {
-  auto size = sqlite3_column_bytes(stmt_, index);
-  auto blob = sqlite3_column_blob(stmt_, index);
-
-  return bytes::View{blob, static_cast<size_t>(size)};
-}
-
-auto Statement::GetColumnText(int index) -> std::string_view {
-  std::size_t size = sqlite3_column_bytes(stmt_, index);
-  auto blob = sqlite3_column_text(stmt_, index);
-
-  return std::string_view{reinterpret_cast<const char*>(blob), size};
-}
-
-void Statement::ResetAndClearBindings() {
-  auto rc = sqlite3_clear_bindings(stmt_);
-  if (rc != SQLITE_OK) {
-    throw Error{rc, fmt::format("statement reset bindings failed, err: {}", sqlite3_errmsg(db_))};
-  }
-
-  Reset();
-}
-
-void Statement::Reset() {
-  auto rc = sqlite3_reset(stmt_);
-  if (rc != SQLITE_OK) {
-    throw Error{rc, fmt::format("statement reset failed, err: {}", sqlite3_errmsg(db_))};
-  }
-}
-
-Connection::Connection(const std::string& path)
-  : db_{nullptr}
-{
-  auto rc = sqlite3_open(path.data(), &db_);
+  auto rc = sqlite3_exec(db_, "COMMIT", nullptr, nullptr, &errMsg);
   if (rc != SQLITE_OK) {
-    throw Error{rc, fmt::format("open db failed, err: {}", sqlite3_errmsg(db_))};
+    throw Error(rc, fmt::format("commit transaction failed, err: {}", errMsg));
   }
 }
 
-Connection::~Connection() noexcept {
-  sqlite3_close(db_);
-}
-
-auto Connection::BeginExplicitTransaction() -> Transaction {
+void Connection::Rollback() {
   char* errMsg = nullptr;
   auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
-  auto rc = sqlite3_exec(db_, "BEGIN EXCLUSIVE", nullptr, nullptr, &errMsg);
+
+  auto rc = sqlite3_exec(db_, "ROLLBACK", nullptr, nullptr, &errMsg);
   if (rc != SQLITE_OK) {
-    throw Error(rc, fmt::format("starting transaction failed, err: {}", errMsg));
+    throw Error(rc, fmt::format("rollback transaction failed, err: {}", errMsg));
   }
-
-  return Transaction{db_};
 }
 
-
 void Connection::Execute(const std::string& sql) {
   char* errMsg = nullptr;
   auto freeErr = vereign::core::MakeScopeGuard([&errMsg]{ sqlite3_free(errMsg); });
diff --git a/cpp/src/vereign/sqlite/connection.hh b/cpp/src/vereign/sqlite/connection.hh
index 86bfd6f839847984f26d0eae3d8ba05ef1f1fbb7..d1134045dc4c2434ff20ad5b2841e58a51c5c253 100644
--- a/cpp/src/vereign/sqlite/connection.hh
+++ b/cpp/src/vereign/sqlite/connection.hh
@@ -1,63 +1,13 @@
 #ifndef __VEREIGN_SQLITE_CONNECTION_HH
 #define __VEREIGN_SQLITE_CONNECTION_HH
 
-#include <vereign/bytes/view.hh>
-#include <vereign/bytes/bytes.hh>
-
-#include <string>
+#include <vereign/sqlite/statement.hh>
 
 struct sqlite3;
 struct sqlite3_stmt;
 
 namespace vereign::sqlite {
 
-class Transaction {
-private:
-  friend class Connection;
-  explicit Transaction(sqlite3* db);
-
-public:
-  void Commit();
-  void Rollback();
-
-  Transaction(Transaction&&);
-  auto operator=(Transaction&&) -> Transaction&;
-  ~Transaction();
-
-  Transaction(const Transaction&) = delete;
-  auto operator=(const Transaction&) -> Transaction& = delete;
-
-private:
-  bool finished_;
-  sqlite3* db_;
-};
-
-class Statement {
-private:
-  friend class Connection;
-
-  explicit Statement(sqlite3* db, sqlite3_stmt* stmt);
-
-public:
-  ~Statement();
-
-  void BindBlob(int index, bytes::View blob);
-  void BindText(int index, const std::string& text);
-
-  auto Step() -> bool;
-
-  auto GetColumnBlob(int index) -> bytes::View;
-  auto GetColumnText(int index) -> std::string_view;
-
-  void Reset();
-  void ResetAndClearBindings();
-  void Finalize();
-
-private:
-  sqlite3* db_;
-  sqlite3_stmt* stmt_;
-};
-
 class Connection {
 public:
   Connection(const std::string& path);
@@ -66,9 +16,13 @@ public:
   Connection(const Connection&) = delete;
   auto operator=(const Connection&) -> Connection& = delete;
 
-  auto BeginExplicitTransaction() -> Transaction;
+  void BeginExplicitTransaction();
+  void Commit();
+  void Rollback();
+
   void Execute(const std::string& sql);
   auto Prepare(const std::string& sql) -> Statement;
+
 private:
   sqlite3* db_;
 };
diff --git a/cpp/src/vereign/sqlite/errors.hh b/cpp/src/vereign/sqlite/errors.hh
index 14fa1d81431e2cb4dfe555d292b1952f10bd80c7..00622311b42921a41500d7d7b3cc1f5b0e54f9a2 100644
--- a/cpp/src/vereign/sqlite/errors.hh
+++ b/cpp/src/vereign/sqlite/errors.hh
@@ -5,15 +5,12 @@
 
 namespace vereign::sqlite {
 
-class Error : virtual std::exception {
+class Error : public std::runtime_error {
 public:
-  Error(int code, std::string&& msg)
-    : code_{code},
-      msg_{std::move(msg)}
-  {}
-
-  auto what() const noexcept -> const char* override {
-    return msg_.data();
+  Error(int code, const std::string& msg)
+    : std::runtime_error(msg),
+      code_{code}
+  {
   }
 
   auto code() const noexcept -> int {
@@ -22,7 +19,6 @@ public:
 
 private:
   int code_;
-  std::string msg_;
 };
 
 } // namespace vereign::sqlite
diff --git a/cpp/src/vereign/sqlite/statement.cc b/cpp/src/vereign/sqlite/statement.cc
new file mode 100644
index 0000000000000000000000000000000000000000..0a3d00795383e4498580c1a56265b1ce55d87918
--- /dev/null
+++ b/cpp/src/vereign/sqlite/statement.cc
@@ -0,0 +1,78 @@
+#include <vereign/sqlite/statement.hh>
+#include <vereign/sqlite/errors.hh>
+
+#include <fmt/format.h>
+#include <sqlite3.h>
+
+namespace vereign::sqlite {
+
+Statement::Statement(sqlite3* db, sqlite3_stmt* stmt)
+  : db_{db},
+    stmt_{stmt}
+{}
+
+void Statement::Finalize() {
+  sqlite3_finalize(stmt_);
+}
+
+Statement::~Statement() {
+  sqlite3_finalize(stmt_);
+}
+
+void Statement::BindBlob(int index, bytes::View blob) {
+  auto rc = sqlite3_bind_blob64(stmt_, index, blob.CharData(), blob.Size(), SQLITE_STATIC);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("bind blob parameter failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+void Statement::BindText(int index, const std::string& text) {
+  auto rc = sqlite3_bind_text(stmt_, index, text.data(), text.size(), SQLITE_STATIC);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("bind text parameter failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+auto Statement::Step() -> bool {
+  auto rc = sqlite3_step(stmt_);
+  switch (rc) {
+  case SQLITE_DONE:
+    return true;
+  case SQLITE_ROW:
+    return false;
+  default:
+    throw Error{rc, fmt::format("executing statement failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+auto Statement::GetColumnBlob(int index) -> bytes::View {
+  auto size = sqlite3_column_bytes(stmt_, index);
+  auto blob = sqlite3_column_blob(stmt_, index);
+
+  return bytes::View{blob, static_cast<size_t>(size)};
+}
+
+auto Statement::GetColumnText(int index) -> std::string_view {
+  std::size_t size = sqlite3_column_bytes(stmt_, index);
+  auto blob = sqlite3_column_text(stmt_, index);
+
+  return std::string_view{reinterpret_cast<const char*>(blob), size};
+}
+
+void Statement::ResetAndClearBindings() {
+  auto rc = sqlite3_clear_bindings(stmt_);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("statement reset bindings failed, err: {}", sqlite3_errmsg(db_))};
+  }
+
+  Reset();
+}
+
+void Statement::Reset() {
+  auto rc = sqlite3_reset(stmt_);
+  if (rc != SQLITE_OK) {
+    throw Error{rc, fmt::format("statement reset failed, err: {}", sqlite3_errmsg(db_))};
+  }
+}
+
+} // namespace vereign::sqlite
diff --git a/cpp/src/vereign/sqlite/statement.hh b/cpp/src/vereign/sqlite/statement.hh
new file mode 100644
index 0000000000000000000000000000000000000000..2b8c336d735a5ec33eb673dffa8df0ef87cce676
--- /dev/null
+++ b/cpp/src/vereign/sqlite/statement.hh
@@ -0,0 +1,42 @@
+#ifndef __VEREIGN_SQLITE_STATEMENT_HH
+#define __VEREIGN_SQLITE_STATEMENT_HH
+
+#include <vereign/bytes/view.hh>
+#include <vereign/bytes/bytes.hh>
+
+#include <string>
+
+struct sqlite3;
+struct sqlite3_stmt;
+
+namespace vereign::sqlite {
+
+class Statement {
+private:
+  friend class Connection;
+
+  explicit Statement(sqlite3* db, sqlite3_stmt* stmt);
+
+public:
+  ~Statement();
+
+  void BindBlob(int index, bytes::View blob);
+  void BindText(int index, const std::string& text);
+
+  auto Step() -> bool;
+
+  auto GetColumnBlob(int index) -> bytes::View;
+  auto GetColumnText(int index) -> std::string_view;
+
+  void Reset();
+  void ResetAndClearBindings();
+  void Finalize();
+
+private:
+  sqlite3* db_;
+  sqlite3_stmt* stmt_;
+};
+
+} // namespace vereign::sqlite
+
+#endif // __VEREIGN_SQLITE_STATEMENT_HH
diff --git a/cpp/tests/util/error.hh b/cpp/tests/util/error.hh
new file mode 100644
index 0000000000000000000000000000000000000000..85d7951d333c60aa7c222cab272ff6737798fd95
--- /dev/null
+++ b/cpp/tests/util/error.hh
@@ -0,0 +1,22 @@
+#ifndef __VEREIGN_TEST_UTIL_ERROR_HH
+#define __VEREIGN_TEST_UTIL_ERROR_HH
+
+#include <optional>
+
+namespace vereign::test {
+
+template <class Error, class Fun>
+auto CatchError(Fun fn) -> std::optional<Error> {
+  try {
+    fn();
+
+    return {};
+  } catch (const Error& err) {
+
+    return err;
+  }
+}
+
+} // namespace test
+
+#endif // __VEREIGN_TEST_UTIL_ERROR_HH
diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt
index 172c4032825569568b6a6cba042d4cde96a91360..3994b8bad658abf6c3d90ca345bf4f88ed7a9964 100644
--- a/cpp/tests/vereign/CMakeLists.txt
+++ b/cpp/tests/vereign/CMakeLists.txt
@@ -28,6 +28,8 @@ list(APPEND TESTS_SRC
   restapi/client_test.cc
   restapi/client_session_test.cc
 
+  kvstore/lock_test.cc
+  kvstore/sqlite_storage_test.cc
   kvstore/crypto_storage_test.cc
 
   identity/provider_test.cc
diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
index b381e8e373d18c05e4525f4458eab50f88dd5e98..af314d9690e521b349f9ed7696f8a1f72a072812 100644
--- a/cpp/tests/vereign/kvstore/crypto_storage_test.cc
+++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
@@ -17,6 +17,7 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
   auto storage_path = core::TempFilePath("test_db_");
   core::RemoveFileGuard rm{storage_path};
 
+  // put value
   {
     auto kvstorage = kvstore::SqliteStorage(storage_path.string());
     kvstore::CryptoStorage storage{kvstorage};
@@ -26,6 +27,7 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
     storage.PutBytes("test", bytes::View(v));
   }
 
+  // with another storage instance get the value
   {
     auto kvstorage = kvstore::SqliteStorage(storage_path.string());
     kvstore::CryptoStorage storage{kvstorage};
@@ -36,28 +38,25 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
 
     CHECK(v.View().String() == "test value");
   }
+}
+
+TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") {
+  auto storage_path = core::TempFilePath("test_db_");
+  core::RemoveFileGuard rm{storage_path};
 
   bytes::Buffer big_value{100000};
   crypto::Rand(big_value);
-  {
-    auto kvstorage = kvstore::SqliteStorage(storage_path.string());
-    kvstore::CryptoStorage storage{kvstorage};
+  auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+  kvstore::CryptoStorage storage{kvstorage};
 
-    storage.Reset("foo");
-    storage.PutBytes("test", big_value.View());
-  }
+  storage.Reset("foo");
+  storage.PutBytes("test", big_value.View());
 
-  {
-    auto kvstorage = kvstore::SqliteStorage(storage_path.string());
-    kvstore::CryptoStorage storage{kvstorage};
-
-    bytes::Buffer v;
-    storage.Open("foo");
-    storage.GetBytes("test", v);
+  bytes::Buffer v;
+  storage.GetBytes("test", v);
 
-    REQUIRE(v.Size() == big_value.Size());
+  REQUIRE(v.Size() == big_value.Size());
 
-    auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size());
-    CHECK(cmp == 0);
-  }
+  auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size());
+  CHECK(cmp == 0);
 }
diff --git a/cpp/tests/vereign/kvstore/lock_test.cc b/cpp/tests/vereign/kvstore/lock_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9db7bef8463f09e92b1397d829eb798394d4bb98
--- /dev/null
+++ b/cpp/tests/vereign/kvstore/lock_test.cc
@@ -0,0 +1,62 @@
+#include <vereign/kvstore/sqlite_storage.hh>
+
+#include <vereign/kvstore/lock.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/core/fs.hh>
+#include <vereign/core/lock_guard.hh>
+#include <vereign/core/scope_guard.hh>
+#include <vereign/core/temp.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/sqlite/errors.hh>
+#include <util/error.hh>
+#include <sqlite3.h>
+
+#include <catch2/catch.hpp>
+#include <boost/filesystem.hpp>
+#include <thread>
+#include <chrono>
+#include <limits>
+#include <optional>
+
+using namespace vereign;
+
+TEST_CASE("kvstore::Lock", "[vereign/kvstore]") {
+
+  SECTION("when the lock is released within the allowed retrials, the lock succeeds") {
+    auto storage_path = core::TempFilePath("test_db_");
+    core::RemoveFileGuard rm{storage_path};
+
+    auto foo_storage = kvstore::SqliteStorage(storage_path.string());
+    auto bar_storage = kvstore::SqliteStorage(storage_path.string());
+
+    foo_storage.Lock();
+
+    bool err = false;
+    auto th = std::thread{[&bar_storage, &err]() {
+      try {
+        kvstore::Lock l{bar_storage, std::numeric_limits<int>::max(), std::chrono::milliseconds{10}};
+      } catch (...) {
+        err = true;
+      }
+    }};
+
+    foo_storage.Unlock();
+    th.join();
+  }
+
+  SECTION("when the lock is not released within the allowed retrials, the lock fails") {
+    auto storage_path = core::TempFilePath("test_db_");
+    core::RemoveFileGuard rm{storage_path};
+
+    auto foo_storage = kvstore::SqliteStorage(storage_path.string());
+    auto bar_storage = kvstore::SqliteStorage(storage_path.string());
+
+    foo_storage.Lock();
+
+    CHECK_THROWS_AS(
+      kvstore::Lock(bar_storage, 2, std::chrono::milliseconds{1}),
+      kvstore::LockError
+    );
+  }
+}
diff --git a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..f8408575d564777dd639fa09c9091a7e11000ef4
--- /dev/null
+++ b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
@@ -0,0 +1,97 @@
+#include <vereign/kvstore/sqlite_storage.hh>
+
+#include <vereign/kvstore/lock.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/bytes/view_dump.hh>
+#include <vereign/core/fs.hh>
+#include <vereign/core/scope_guard.hh>
+#include <vereign/core/temp.hh>
+#include <vereign/crypto/rand.hh>
+#include <vereign/sqlite/errors.hh>
+#include <util/error.hh>
+#include <sqlite3.h>
+
+#include <catch2/catch.hpp>
+#include <boost/filesystem.hpp>
+#include <thread>
+#include <optional>
+
+using namespace vereign;
+
+
+TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") {
+  auto storage_path = core::TempFilePath("test_db_");
+  core::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+
+  kvstorage.PutInt64("foo", 42);
+  kvstorage.PutInt64("bar", 422);
+
+  CHECK(kvstorage.GetInt64("foo") == 42);
+  CHECK(kvstorage.GetInt64("bar") == 422);
+
+  kvstorage.DeleteAll();
+
+  CHECK(kvstorage.GetInt64("foo") == 0);
+  CHECK(kvstorage.GetInt64("bar") == 0);
+}
+
+TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") {
+
+  SECTION("when locked using lock guard, it must unlock on scope exit") {
+    auto storage_path = core::TempFilePath("test_db_");
+    core::RemoveFileGuard rm{storage_path};
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+
+      kvstore::Lock l{kvstorage};
+
+      kvstorage.PutInt64("foo", 42);
+      kvstorage.PutInt64("bar", 422);
+    }
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+
+      kvstore::Lock l{kvstorage};
+
+      CHECK(kvstorage.GetInt64("foo") == 42);
+      CHECK(kvstorage.GetInt64("bar") == 422);
+    }
+  }
+
+  SECTION("when locked, it must unlock on scope exit") {
+    auto storage_path = core::TempFilePath("test_db_");
+    core::RemoveFileGuard rm{storage_path};
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+      kvstorage.Lock();
+
+      kvstorage.PutInt64("foo", 42);
+      kvstorage.PutInt64("bar", 422);
+    }
+
+    {
+      auto kvstorage = kvstore::SqliteStorage(storage_path.string());
+      kvstorage.Lock();
+
+      CHECK(kvstorage.GetInt64("foo") == 42);
+      CHECK(kvstorage.GetInt64("bar") == 422);
+    }
+  }
+
+  SECTION("when the storage is already locked, it must fail with LockError") {
+    auto storage_path = core::TempFilePath("test_db_");
+    core::RemoveFileGuard rm{storage_path};
+
+    auto foo_storage = kvstore::SqliteStorage(storage_path.string());;
+    auto bar_storage = kvstore::SqliteStorage(storage_path.string());
+
+    foo_storage.Lock();
+
+    CHECK_THROWS_AS(bar_storage.Lock(), kvstore::LockError);
+  }
+}