diff --git a/cpp/src/CMakeLists.txt b/cpp/src/CMakeLists.txt
index 1ad9784dd73c9b9c756253e4c86cf5dd72fbed2f..520ad243c7ff982e01c06d0d7539274c96135545 100644
--- a/cpp/src/CMakeLists.txt
+++ b/cpp/src/CMakeLists.txt
@@ -71,6 +71,7 @@ set(VEREIGNLIB_SRC
   vereign/encoding/base64.cc
   vereign/encoding/hex.cc
 
+  vereign/crypto/rand.cc
   vereign/crypto/aes.cc
   vereign/crypto/rsa.cc
   vereign/crypto/bio.cc
@@ -80,6 +81,7 @@ set(VEREIGNLIB_SRC
   vereign/kvstore/detail/base_crypto_storage.cc
   vereign/kvstore/sqlite_storage.cc
   vereign/kvstore/crypto_storage.cc
+  vereign/kvstore/detail/value_encoder.cc
 
   vereign/identity/provider.cc
 
diff --git a/cpp/src/csandbox.cc b/cpp/src/csandbox.cc
index b6101f95b0638e92932a58599140295e224982ae..44b33e62bf2e2fef4195d45b73459ebcc0c7a9b2 100644
--- a/cpp/src/csandbox.cc
+++ b/cpp/src/csandbox.cc
@@ -1,24 +1,8 @@
-#include "vereign/crypto/rand.hh"
-#include "vereign/kvstore/sqlite_storage.hh"
-#include <boost/core/ignore_unused.hpp>
-#include <iostream>
-#include <boost/filesystem.hpp>
-#include <openssl/digest.h>
-#include <openssl/evp.h>
-#include <vereign/core/string.hh>
 
-#include <vereign/bytes/view.hh>
-#include <vereign/bytes/view_dump.hh>
-#include <vereign/fs/util.hh>
-#include <vereign/fs/path.hh>
-#include <vereign/fs/operations.hh>
-
-
-using namespace vereign;
 
 auto main(int argc, char** argv) -> int {
-  boost::ignore_unused(argc);
-  boost::ignore_unused(argv);
+  argc = 0;
+  argv = nullptr;
 
   return 0;
 }
diff --git a/cpp/src/vereign/bytes/buffer.cc b/cpp/src/vereign/bytes/buffer.cc
index 30e202dcd66dc63869a99cdfcde147d11a0e663e..546a4228f372ea30a4dc914b5c66f054ce1c1527 100644
--- a/cpp/src/vereign/bytes/buffer.cc
+++ b/cpp/src/vereign/bytes/buffer.cc
@@ -1,8 +1,9 @@
-#include <memory>
-#include <new>
-#include <stdexcept>
 #include <vereign/bytes/buffer.hh>
 
+#include <vereign/bytes/errors.hh>
+
+#include <memory>
+
 namespace vereign::bytes {
 
 Buffer::Buffer() noexcept
@@ -111,9 +112,9 @@ void Buffer::Reserve(std::size_t size) {
     return;
   }
 
-  auto cap = cap_;
-  while (size > cap - size_) {
-    cap = cap * 2;
+  auto cap = cap_ * 2;
+  if (size > cap - size_) {
+    cap = size_ + size;
   }
 
   auto newData = reinterpret_cast<uint8_t*>(std::realloc(data_, cap));
@@ -129,12 +130,12 @@ void Buffer::Reset() {
   size_ = 0;
 }
 
-void Buffer::IncSize(std::size_t size) {
-  if (size_ + size > cap_) {
-    throw std::runtime_error("cannot increment size pass the capacity");
+void Buffer::IncSize(std::size_t val) {
+  if (size_ + val > cap_) {
+    throw IncrementOutOfBounds{};
   }
 
-  size_ += size;
+  size_ += val;
 }
 
 auto Buffer::WriteWithinCap(bytes::View src) noexcept -> std::size_t {
@@ -162,7 +163,7 @@ auto Buffer::View(std::size_t start) const noexcept -> bytes::View {
 
 auto Buffer::operator[](std::size_t index) -> uint8_t& {
   if (index >= cap_) {
-    throw std::runtime_error("index out of bounds");
+    throw IndexOutOfBounds{};
   }
 
   return data_[index];
@@ -170,7 +171,7 @@ auto Buffer::operator[](std::size_t index) -> uint8_t& {
 
 auto Buffer::operator[](std::size_t index) const -> const uint8_t& {
   if (index >= cap_) {
-    throw std::runtime_error("index out of bounds");
+    throw IndexOutOfBounds{};
   }
 
   return data_[index];
diff --git a/cpp/src/vereign/bytes/buffer.hh b/cpp/src/vereign/bytes/buffer.hh
index c2fe622b87d3d7bb15c9b094b1d458c1bc22f9eb..6d0e5c7a8bddee458283afc810b3b79765e76968 100644
--- a/cpp/src/vereign/bytes/buffer.hh
+++ b/cpp/src/vereign/bytes/buffer.hh
@@ -6,12 +6,45 @@
 
 namespace vereign::bytes {
 
+/**
+ * Dynamically expandable memory buffer.
+ *
+ * The buffer is a 3-tuple - pointer, size and capacity.
+ * Typically used in functions for output parameters and return values.
+ * Provides API that is easy to use with C APIs.
+ *
+ * The buffer is move only.
+ */
 class Buffer {
 public:
+  /**
+   * Creates empty buffer.
+   */
   Buffer() noexcept;
+
+  /**
+   * Creates a buffer with reserved memory capacity.
+   *
+   * The size of the buffer is zero.
+   *
+   * @param cap The capacity of the buffer.
+   *
+   * @throws std::bad_alloc when memory reservation fails.
+   */
   Buffer(std::size_t cap);
+
+  /**
+   * Creates a buffer by copying from source bytes view.
+   *
+   * @param src The source that will be copied from.
+   *
+   * @throws std::bad_alloc when memory reservation fails.
+   */
   Buffer(View src);
 
+  /**
+   * The buffer is movable.
+   */
   Buffer(Buffer&& other) noexcept;
   auto operator=(Buffer&& other) noexcept -> Buffer&;
 
@@ -19,28 +52,216 @@ public:
   Buffer(const Buffer&) = delete;
   auto operator=(const Buffer&) -> Buffer& = delete;
 
+  /**
+   * Frees the buffer memory.
+   */
   ~Buffer();
 
+  /**
+   * Returns a pointer to the first byte of the buffer.
+   *
+   * Buffer::begin(), Buffer::end() pair is useful for range loops.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo")};
+   * for (const auto& byte : buf) {
+   *   byte = 'x';
+   * }
+   *
+   * assert(buf.View().String() == "xxx");
+   * @endcode
+   */
   auto begin() noexcept -> uint8_t*;
+
+  /**
+   * Returns a pointer to the first byte of the buffer.
+   *
+   * Buffer::begin(), Buffer::end() pair is useful for range loops.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo bar")};
+   * std::string s;
+   *
+   * for (const auto& byte : buf) {
+   *   s += byte;
+   * }
+   *
+   * assert(s == "foo bar");
+   * @endcode
+   */
   auto begin() const noexcept -> const uint8_t*;
 
+  /**
+   * Returns a pointer to the byte following the last byte in the buffer.
+   *
+   * Note that this is the last byte in the range [0, size).
+   * It is often used when calling C APIs.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo bar")};
+   *
+   * buf.Reserve(4);
+   * std::strncpy((char*)buf.end(), " baz", 4);
+   * buf.IncSize(4);
+   *
+   * assert(buf.View().String() == "foo bar baz");
+   * @endcode
+   */
   auto end() noexcept -> uint8_t*;
+
+  /**
+   * Returns a read only pointer to the byte following the last byte in the buffer.
+   *
+   * Note that this is the last byte in the range [0, size).
+   */
   auto end() const noexcept -> const uint8_t*;
 
+  /**
+   * Access a byte in the range of [0, cap).
+   *
+   * @param index The index of the byte to access.
+   *
+   * @throws std::runtime_error when the passed index is out of bounds.
+   */
   auto operator[](std::size_t index) -> uint8_t&;
+
+  /**
+   * Read only access a byte in the range of [0, cap).
+   *
+   * @param index The index of the byte to access.
+   *
+   * @throws std::runtime_error when the passed index is out of bounds.
+   */
   auto operator[](std::size_t index) const -> const uint8_t&;
 
+  /**
+   * Retrieve buffer size.
+   *
+   * @returns the buffer size.
+   */
   auto Size() const noexcept -> std::size_t;
+
+  /**
+   * Retrieve buffer capacity.
+   *
+   * @returns the buffer capacity.
+   */
   auto Cap() const noexcept -> std::size_t;
+
+  /**
+   * Reserve memory, so that there is at least `size` free capacity.
+   *
+   * If there is already enough free capacity, no memory allocation is done.
+   * The allocated memory may be bigger than what is needed.
+   *
+   * If the call is successful then it is guaranteed that `this->FreeCap() >= size`.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("foo bar")};
+   *
+   * buf.Reserve(4); // ensure there will be a free capacity for 4 bytes
+   * std::strncpy((char*)buf.end(), " baz", 4); // copy the bytes
+   * buf.IncSize(4); // update the buffer size with the newly written bytes
+   *
+   * assert(buf.View().String() == "foo bar baz");
+   * @endcode
+   *
+   * @param size The desired free capacity.
+   *
+   * @throws std::bad_alloc when memory allocation fails.
+   */
   void Reserve(std::size_t size);
+
+  /**
+   * Sets the buffer size to zero.
+   *
+   * This does not free any memory, so the capacity stays the intact, and the buffer can be reused.
+   */
   void Reset();
 
-  void IncSize(std::size_t size);
+  /**
+   * Increments the size of the buffer.
+   *
+   * It is typically used after some function has written bytes to the end of the buffer.
+   *
+   * @param val The value that will be added to the current size.
+   */
+  void IncSize(std::size_t val);
+
+  /**
+   * Retrieve the buffer free capacity.
+   *
+   * This is equal to `this->Cap() - this->Size()`.
+   */
   auto FreeCap() const noexcept -> std::size_t;
+
+  /**
+   * Adds bytes up to the currently available buffer capacity.
+   *
+   * After the operation succeeds, the buffer size will be incremented with the number of bytes
+   * that have been copied.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{3};
+   * buf.WriteWithinCap(bytes::View("foo bar"));
+   *
+   * // only 3 bytes are written
+   * assert(buf.View.String() == "foo");
+   * @endcode
+   *
+   * @param src The source that will be appended to the buffer.
+   * @returns The amount of bytes that were actually copied into the buffer.
+   */
   auto WriteWithinCap(bytes::View src) noexcept -> std::size_t;
+
+  /**
+   * Adds a source view of bytes to the buffer.
+   *
+   * If the buffer does not have enough capacity, it will be expanded.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{3};
+   * buf.WriteWithinCap(bytes::View("foo bar"));
+   *
+   * // all bytes are written
+   * assert(buf.View.String() == "foo bar");
+   * @endcode
+   *
+   * @param The source that will be appended to the buffer.
+   * @returns The amount of bytes that were copied into the buffer. That is equal to src.Size().
+   */
   auto Write(bytes::View src) -> std::size_t;
 
+  /**
+   * Retrieve a read only view of the buffer.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("123")};
+   * assert(buf.View().String() == "123");
+   * @endcode
+   *
+   * @returns a read only view range [0, this->Size()).
+   */
   auto View() const noexcept -> bytes::View;
+
+  /**
+   * Retrieve a read only view of the buffer staring from a given offset.
+   *
+   * Example:
+   * @code
+   * auto buf = bytes::Buffer{bytes::View("123")};
+   * assert(buf.View(1).String() == "23");
+   * @endcode
+   *
+   * @returns a read only view range [start, this->Size()).
+   */
   auto View(std::size_t start) const noexcept -> bytes::View;
 
 private:
diff --git a/cpp/src/vereign/bytes/errors.hh b/cpp/src/vereign/bytes/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..ac7e9e66a800a30f340e6b3f0de9c9cbf959cabd
--- /dev/null
+++ b/cpp/src/vereign/bytes/errors.hh
@@ -0,0 +1,35 @@
+#ifndef __VEREIGN_BYTES_ERRORS_HH
+#define __VEREIGN_BYTES_ERRORS_HH
+
+#include <stdexcept>
+
+namespace vereign::bytes {
+
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error{what}
+  {
+
+  }
+};
+
+class IndexOutOfBounds : public Error {
+public:
+  IndexOutOfBounds()
+    : Error{"index out of bounds"}
+  {
+  }
+};
+
+class IncrementOutOfBounds : public Error {
+public:
+  IncrementOutOfBounds()
+    : Error{"cannot increment size pass the capacity"}
+  {
+  }
+};
+
+} // namespace vereign::bytes
+
+#endif // __VEREIGN_BYTES_ERRORS_HH
diff --git a/cpp/src/vereign/bytes/view.hh b/cpp/src/vereign/bytes/view.hh
index 672b6229ada16d3b2b1a1351ddcc3865d30a2e3e..4520934c927170ed79682ab95d9f9ed5e430dee8 100644
--- a/cpp/src/vereign/bytes/view.hh
+++ b/cpp/src/vereign/bytes/view.hh
@@ -1,6 +1,8 @@
 #ifndef __VEREIGN_BYTES_VIEW_HH
 #define __VEREIGN_BYTES_VIEW_HH
 
+#include <vereign/bytes/errors.hh>
+
 #include <cstring>
 #include <string>
 #include <string_view>
@@ -9,37 +11,79 @@
 
 namespace vereign::bytes {
 
+/**
+ * Bytes view represents a read only access to a range of bytes.
+ *
+ * The View is a 2-tuple with pointer and size.
+ * Typically used in functions for input parameters.
+ *
+ * **NOTE: The View does not own the memory that it references.**
+ */
 class View {
 public:
+  /**
+   * Creates empty view.
+   */
   View() = default;
 
+  /**
+   * Creates a view from raw pointer and a size.
+   *
+   * @param data Pointer to the memory.
+   * @param size The size of the memory.
+   */
   View(const uint8_t* data, std::size_t size) noexcept
     : size_{size},
       data_{data}
   {
   }
 
+  /**
+   * Create a view from a string view.
+   *
+   * @param str The input string view.
+   */
   View(std::string_view str) noexcept
     : size_{str.length()},
       data_{str.length() > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
   {
   }
 
+  /**
+   * Creates a view from wide string view.
+   *
+   * @param str The input string.
+   */
   View(std::wstring_view str) noexcept
     : size_{str.length() * sizeof(wchar_t)},
       data_{size_ > 0 ? reinterpret_cast<const uint8_t*>(str.data()): nullptr}
   {
   }
 
+  /**
+   * Creates a view from void pointer and a size.
+   *
+   * @param data Pointer to the memory.
+   * @param size The size of the memory.
+   */
   View(const void* ptr, std::size_t size) noexcept
     : size_{size},
       data_{static_cast<const uint8_t*>(ptr)}
   {
   }
 
+  // default copyable.
   View(const View&) = default;
   auto operator=(const View&) -> View& = default;
 
+  /**
+   * Slice returns a new view in the interval [start, size).
+   *
+   * If the start is bigger than the size of the slice it returns empty view.
+   *
+   * @param start The beginning of the new View.
+   * @returns a new view in the interval [start, size).
+   */
   auto Slice(std::size_t start) const -> View {
     if (start >= size_) {
       return View(data_, 0);
@@ -48,6 +92,15 @@ public:
     return View(data_ + start, size_ - start);
   }
 
+  /**
+   * Slice returns a new view in the interval [start, end).
+   *
+   * If the start is bigger than the size of the slice it returns empty view.
+   * If the end is bigger than the size of the slice it returns [start, size).
+   *
+   * @param start The beginning of the new View.
+   * @returns a new view in the interval [start, size).
+   */
   auto Slice(std::size_t start, std::size_t end) const -> View {
     if (start >= size_) {
       return View(data_, 0);
@@ -56,22 +109,65 @@ public:
     return View(data_ + start, std::min(size_, end) - start);
   }
 
+  /**
+   * Retrieve a pointer to the data.
+   *
+   * @returns a pointer to the data.
+   */
   auto Data() const noexcept -> const uint8_t* {
     return data_;
   }
 
+  /**
+   * Retrieve a char pointer to the data.
+   *
+   * @returns a char pointer to the data.
+   */
   auto CharData() const noexcept -> const char* {
     return reinterpret_cast<const char*>(data_);
   }
 
+  /**
+   * Retrieve a wide char pointer to the data.
+   *
+   * @returns a wide char pointer to the data.
+   */
+  auto WideCharData() const noexcept -> const wchar_t* {
+    return reinterpret_cast<const wchar_t*>(data_);
+  }
+
+  /**
+   * Retrieve view size.
+   *
+   * @returns view size.
+   */
   auto Size() const noexcept -> std::size_t {
     return size_;
   }
 
+  /**
+   * Retrieve a string view of the data.
+   *
+   * @returns a string view of the data.
+   */
   auto String() const noexcept -> std::string_view {
     return std::string_view{CharData(), size_};
   }
 
+  /**
+   * Retrieve a wide string view of the data.
+   *
+   * @returns a wide string view of the data.
+   */
+  auto WideString() const noexcept -> std::wstring_view {
+    return std::wstring_view{WideCharData(), size_/sizeof(wchar_t)};
+  }
+
+  /**
+   * Binary compare the contents of two views.
+   *
+   * @returns true if the views are of the same size and if all the bytes in the two views are equal.
+   */
   auto operator==(View other) const noexcept -> bool {
     if (size_ != other.size_) {
       return false;
@@ -80,13 +176,26 @@ public:
     return std::memcmp(data_, other.data_, size_) == 0;
   }
 
+  /**
+   * Binary compare the contents of two views.
+   *
+   * @returns true if the views are of different size or if the bytes in the two views are not equal.
+   */
   auto operator!=(View other) const noexcept -> bool {
     return !(*this == other);
   }
 
+  /**
+   * Access a single byte in the view.
+   *
+   * @param index The index of the byte that will be returned.
+   * @returns the byte at the specified index.
+   *
+   * @throws bytes::IndexOutOfBounds when the index is out of bounds.
+   */
   auto operator[](std::size_t index) const -> const uint8_t& {
     if (index >= size_ ) {
-      throw std::runtime_error("index out of bounds");
+      throw IndexOutOfBounds{};
     }
 
     return data_[index];
diff --git a/cpp/src/vereign/crypto/aes.cc b/cpp/src/vereign/crypto/aes.cc
index 59875fb3dd19c268bf8d86f591f008c3109de9ae..f9bcefab4365b17f31e960ffb802d662692695d2 100644
--- a/cpp/src/vereign/crypto/aes.cc
+++ b/cpp/src/vereign/crypto/aes.cc
@@ -19,11 +19,11 @@ void GCM256Encrypt(
   bytes::View src,
   bytes::View key,
   bytes::Buffer& iv,
-  bytes::Buffer& encrypted,
-  bytes::Buffer& tag
+  bytes::Buffer& tag,
+  bytes::Buffer& encrypted
 ) {
   iv.Reserve(gcmIVSizeBytes);
-  crypto::Rand(iv);
+  crypto::Rand(iv, gcmIVSizeBytes);
 
   encrypted.Reserve(src.Size() + aes256BlockSizeBytes);
 
@@ -61,7 +61,7 @@ void GCM256Encrypt(
   encrypted.IncSize(bytes_written);
 
   tag.Reserve(gcmTagSizeBytes);
-  r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, tag.FreeCap(), tag.end());
+  r = EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, gcmTagSizeBytes, tag.end());
   if (r != 1) {
     throw OpenSSLError("getting GCM tag failed");
   }
diff --git a/cpp/src/vereign/crypto/aes.hh b/cpp/src/vereign/crypto/aes.hh
index 861c749b8757cb7188824de79aa430ed246d6d41..09be668b964bddd8f31aecc93e874ae35834eb3b 100644
--- a/cpp/src/vereign/crypto/aes.hh
+++ b/cpp/src/vereign/crypto/aes.hh
@@ -1,19 +1,72 @@
 #ifndef __VEREIGN_CRYPTO_AES_HH
 #define __VEREIGN_CRYPTO_AES_HH
 
-#include "vereign/bytes/view.hh"
 #include <vereign/bytes/buffer.hh>
 
+/**
+ * Provides utilities for AES encryption/decryption.
+ */
 namespace vereign::crypto::aes {
 
+/**
+ * Encrypt given bytes with AES256-GCM.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::Rand(32); // 256 bits
+ *
+ * bytes::Buffer iv;
+ * bytes::Buffer tag;
+ * bytes::Buffer encrypted;
+ *
+ * crypto::aes::GCM256Encrypt(bytes::View(input), key.View(), iv, tag, encrypted);
+ * @endcode
+ *
+ * @param src The bytes that will be encrypted.
+ * @param key The AES 256 bit key.
+ * @param iv The initialization vector that was used during the encryption.
+ * @param tag The authentication tag that was produced during the encryption.
+ * @param encrypted The result of the encryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 void GCM256Encrypt(
   bytes::View src,
   bytes::View key,
   bytes::Buffer& iv,
-  bytes::Buffer& encrypted,
-  bytes::Buffer& tag
+  bytes::Buffer& tag,
+  bytes::Buffer& encrypted
 );
 
+/**
+ * Decrypts given bytes with AES256-GCM.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::Rand(32); // 256 bits
+ *
+ * bytes::Buffer iv;
+ * bytes::Buffer tag;
+ * bytes::Buffer encrypted;
+ *
+ * crypto::aes::GCM256Encrypt(bytes::View(input), key.View(), iv, tag, encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * crypto::aes::GCM256Decrypt(encrypted.View(), key.View(), iv.View(), tag.View(), decrypted);
+ *
+ * assert(input == decrypted.View().String());
+ * @endcode
+ *
+ * @param src The bytes that will be decrypted.
+ * @param key The AES 256 bit key.
+ * @param iv The initialization vector that was used during the encryption.
+ * @param tag The authentication tag that was produced during the encryption.
+ * @param encrypted The result of the decryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 void GCM256Decrypt(
   bytes::View src,
   bytes::View key,
diff --git a/cpp/src/vereign/crypto/bio.hh b/cpp/src/vereign/crypto/bio.hh
index 8c1721430878d422d48de31e13d6f0931fa978b4..2bd5cedacee0b6c94c81a0f2af50e448982b6d3f 100644
--- a/cpp/src/vereign/crypto/bio.hh
+++ b/cpp/src/vereign/crypto/bio.hh
@@ -6,6 +6,12 @@
 
 namespace vereign::crypto::bio {
 
+/**
+ * Creates a bytes::View for given OpenSSL BIO.
+ *
+ * @param bio The input BIO.
+ * @returns a bytes view of OpenSSL BIO.
+ */
 auto View(const BIO* bio) -> bytes::View;
 
 } // vereign::crypto::bio
diff --git a/cpp/src/vereign/crypto/digest.hh b/cpp/src/vereign/crypto/digest.hh
index 53f15d5b73791c6c3453665b541effe1e4f01968..365d45e9af3fa5fd3be617d539aaa823ac210af0 100644
--- a/cpp/src/vereign/crypto/digest.hh
+++ b/cpp/src/vereign/crypto/digest.hh
@@ -5,6 +5,22 @@
 
 namespace vereign::crypto::digest {
 
+/**
+ * Creates a SHA1 hash of given bytes.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ *
+ * bytes::Buffer hash;
+ * crypto::digest::sha1(bytes::View(data), hash);
+ * @endcode
+ *
+ * @param src The input bytes.
+ * @param result The SHA1 hash of the input.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 void sha1(bytes::View src, bytes::Buffer& result);
 
 } // vereign::crypto::digest
diff --git a/cpp/src/vereign/crypto/errors.hh b/cpp/src/vereign/crypto/errors.hh
index c5282ade4ae3a4ab91ecc0b60fbb479e8b88a47c..949eb248cd8a0caf3014701a8e962c47f866f1ad 100644
--- a/cpp/src/vereign/crypto/errors.hh
+++ b/cpp/src/vereign/crypto/errors.hh
@@ -6,6 +6,9 @@
 
 namespace vereign::crypto {
 
+/**
+ * The base error type for the namespace vereign::crypto.
+ */
 class Error : public std::runtime_error {
 public:
   Error(const std::string& what)
@@ -14,6 +17,9 @@ public:
   }
 };
 
+/**
+ * An error thrown by the crypto functions.
+ */
 class OpenSSLError : public Error {
 public:
   OpenSSLError(const std::string& what)
diff --git a/cpp/src/vereign/crypto/rand.cc b/cpp/src/vereign/crypto/rand.cc
new file mode 100644
index 0000000000000000000000000000000000000000..2acc168ed6d9df6905c3ad25e7053c041e29a715
--- /dev/null
+++ b/cpp/src/vereign/crypto/rand.cc
@@ -0,0 +1,26 @@
+#include <vereign/crypto/rand.hh>
+
+#include <openssl/rand.h>
+#include <vereign/crypto/errors.hh>
+
+namespace vereign::crypto {
+
+void Rand(bytes::Buffer& buf, std::size_t size) {
+  buf.Reserve(size);
+  int result = RAND_bytes(buf.end(), size);
+  if (result == 0) {
+    ERR_clear_error();
+    throw Error("crypto rand failed");
+  }
+
+  buf.IncSize(size);
+}
+
+auto Rand(std::size_t size) -> bytes::Buffer {
+  bytes::Buffer buf{size};
+  Rand(buf, size);
+
+  return buf;
+}
+
+} // vereign::crypto
diff --git a/cpp/src/vereign/crypto/rand.hh b/cpp/src/vereign/crypto/rand.hh
index 4067b039adf2b80688b3f7af93340730b05081eb..4c3979900f1464d2e54c9957f689f503af3e7e2f 100644
--- a/cpp/src/vereign/crypto/rand.hh
+++ b/cpp/src/vereign/crypto/rand.hh
@@ -2,21 +2,46 @@
 #define __VEREIGN_CRYPTO_RAND_HH
 
 #include <vereign/bytes/buffer.hh>
-#include <vereign/crypto/errors.hh>
-
-#include <openssl/rand.h>
 
 namespace vereign::crypto {
 
-inline void Rand(bytes::Buffer& buf) {
-  int result = RAND_bytes(buf.end(), buf.FreeCap());
-  if (result == 0) {
-    ERR_clear_error();
-    throw Error("crypto rand failed");
-  }
+/**
+ * Appends a random bytes to a buffer.
+ *
+ * After the operation is finished, the passed `buf` will be with incremented size of the newly
+ * added random bytes.
+ *
+ * Example:
+ * @code
+ * bytes::Bytes buf;
+ * crypto::Rand(buf, 16);
+ *
+ * assert(buf.Size() == 16);
+ * @endcode
+ *
+ * @param buf The buffer that will be filled with random bytes.
+ * @param size The number of the random bytes.
+ *
+ * @throws crypto::Error on failure.
+ */
+void Rand(bytes::Buffer& buf, std::size_t size);
 
-  buf.IncSize(buf.FreeCap());
-}
+/**
+ * Generate random bytes.
+ *
+ * Example:
+ * @code
+ * auto buf = crypto::Rand(16);
+ *
+ * assert(buf.Size() == 16);
+ * @endcode
+ *
+ * @param size The number of the random bytes.
+ * @returns buffer with the generated random bytes.
+ *
+ * @throws crypto::Error on failure.
+ */
+auto Rand(std::size_t size) -> bytes::Buffer;
 
 } // vereign::crypto
 
diff --git a/cpp/src/vereign/crypto/rsa.hh b/cpp/src/vereign/crypto/rsa.hh
index ab41d09e702ca3b6a9106a77ff5cbb2d1bd93fc8..4d8e18fc38701d7a921dc2ea42a2781030ee32a6 100644
--- a/cpp/src/vereign/crypto/rsa.hh
+++ b/cpp/src/vereign/crypto/rsa.hh
@@ -9,15 +9,137 @@
 
 namespace vereign::crypto::rsa {
 
+/**
+ * Generates new RSA key.
+ *
+ * Example:
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ * @endcode
+ *
+ * @param bits The length of the key.
+ * @returns the newly generated key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 auto GenerateKey(int bits) -> bssl::UniquePtr<EVP_PKEY>;
 
+/**
+ * Encrypts given bytes with RSA public key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::rsa::GenerateKey(2048);
+ * bytes::Buffer encrypted;
+ *
+ * crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+ *
+ * assert(decrypted.View() == bytes.View(input));
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be encrypted.
+ * @param encrypted The result of the encryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 void PublicKeyEncrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& encrypted);
+
+/**
+ * Decrypts given bytes with RSA private key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto key = crypto::rsa::GenerateKey(2048);
+ * bytes::Buffer encrypted;
+ *
+ * crypto::rsa::PublicKeyEncrypt(key.get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * crypto::rsa::PrivateKeyDecrypt(key.get(), encrypted.View(), decrypted);
+ *
+ * assert(decrypted.View() == bytes.View(input));
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be decrypted.
+ * @param decrypted The result of the decryption.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 void PrivateKeyDecrypt(EVP_PKEY* key, bytes::View src, bytes::Buffer& decrypted);
 
+/**
+ * Exports a public key part to PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get());
+ * std::cout << crypto::bio::View(bio.get()).String() << std::endl;
+ * @endcode
+ *
+ * @param key The key to export.
+ * @returns a memory BIO with the exported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 auto ExportPublicKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
+
+/**
+ * Import public key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPublicKeyToPEM(key.get());
+ * auto imported_key = crypto::rsa::ImportPublicKeyFromPEM(crypto::bio::View(bio.get()));
+ * @endcode
+ *
+ * @param pem PEM encoded key.
+ * @returns imported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 auto ImportPublicKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>;
 
+/**
+ * Export private key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+ * std::cout << crypto::bio::View(bio.get()).String() << std::endl;
+ * @endcode
+ *
+ * @param key The key to export.
+ * @returns a memory BIO with the exported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 auto ExportPrivateKeyToPEM(EVP_PKEY* key) -> bssl::UniquePtr<BIO>;
+
+/**
+ * Import private key from PEM format.
+ *
+ * @code
+ * auto key = crypto::rsa::GenerateKey(2048);
+ *
+ * auto bio = crypto::rsa::ExportPrivateKeyToPEM(key.get());
+ * auto imported_key = crypto::rsa::ImportPrivateKeyFromPEM(crypto::bio::View(bio.get()));
+ * @endcode
+ *
+ * @param pem PEM encoded key.
+ * @returns imported key.
+ *
+ * @throws crypto::OpenSSLError on failure.
+ */
 auto ImportPrivateKeyFromPEM(bytes::View pem) -> bssl::UniquePtr<EVP_PKEY>;
 
 } // vereign::crypto::rsa
diff --git a/cpp/src/vereign/encoding/base64.hh b/cpp/src/vereign/encoding/base64.hh
index 2bfaab5f159151b2a10330bf176ef7e0394ae167..a69a559403f9126045cdb69e89bbf084a519fd08 100644
--- a/cpp/src/vereign/encoding/base64.hh
+++ b/cpp/src/vereign/encoding/base64.hh
@@ -6,7 +6,40 @@
 
 namespace vereign::encoding::base64 {
 
+/**
+ * Encodes source bytes into base64 encoding.
+ *
+ * No new lines are inserted.
+ *
+ * Example:
+ * @code
+ * std::string s{"foob"};
+ * bytes::Buffer encoded;
+ * encoding::base64::Encode(bytes::View(s), encoded);
+ *
+ * assert(encoded.View().String() == "Zm9vYg==")
+ * @endcode
+ *
+ * @param src The source bytes that will be encoded.
+ * @param encoded The encoded bytes.
+ */
 void Encode(bytes::View src, bytes::Buffer& encoded);
+
+/**
+ * Decodes base64 encoded bytes.
+ *
+ * Example:
+ * @code
+ * std::string s{"Zm9vYg=="};
+ * bytes::Buffer decoded;
+ * encoding::base64::Decode(bytes::View(s), decoded);
+ *
+ * assert(decoded.View().String() == "foob");
+ * @endcode
+ *
+ * @param src The base64 encoded bytes that will be decoded.
+ * @param decoded The decoded bytes.
+ */
 void Decode(bytes::View src, bytes::Buffer& decoded);
 
 } // vereign::encoding::base64
diff --git a/cpp/src/vereign/encoding/binary.cc b/cpp/src/vereign/encoding/binary.cc
index 0358fc236762f3d09cbf6ddd8a66f970e5f9a570..3c25446ccf77e123d6c19ec71578a38ab895c34e 100644
--- a/cpp/src/vereign/encoding/binary.cc
+++ b/cpp/src/vereign/encoding/binary.cc
@@ -1,5 +1,7 @@
 #include <vereign/encoding/binary.hh>
 
+#include <vereign/encoding/errors.hh>
+
 namespace vereign::encoding::binary {
 
 void EncodeUint8(bytes::Buffer& out, uint8_t v) {
@@ -10,7 +12,7 @@ void EncodeUint8(bytes::Buffer& out, uint8_t v) {
 
 auto DecodeUint8(bytes::View& b) -> uint8_t {
   if (b.Size() < 1) {
-    throw std::runtime_error("decoding failed the input size is less than 1 bytes");
+    throw encoding::Error("decoding failed the input size is less than 1 bytes");
   }
 
   return b.Data()[0];
@@ -46,8 +48,7 @@ auto DecodeUint64(const uint8_t* b) -> uint64_t {
 
 auto DecodeUint64(bytes::View b) -> uint64_t {
   if (b.Size() < 8) {
-    // FIXME: add encoding::Error exception
-    throw std::runtime_error("decoding failed the input size is less than 8 bytes");
+    throw encoding::Error("decoding failed the input size is less than 8 bytes");
   }
 
   return DecodeUint64(b.Data());
@@ -61,7 +62,7 @@ void EncodeBytes(bytes::Buffer& out, bytes::View bytes) {
 auto DecodeBytes(bytes::View b, bytes::View& out) -> std::size_t {
   auto size = DecodeUint64(b);
   if (size < 0 || size + 8 > b.Size()) {
-    throw std::runtime_error("decode bytes failed: invalid size");
+    throw encoding::Error("decode bytes failed: invalid size");
   }
 
   out = b.Slice(8, size + 8);
diff --git a/cpp/src/vereign/encoding/binary.hh b/cpp/src/vereign/encoding/binary.hh
index 8d59c655446028fedec33532674166111b752930..8be71c31f3a76f1c4c36c7e631f8731253edd64d 100644
--- a/cpp/src/vereign/encoding/binary.hh
+++ b/cpp/src/vereign/encoding/binary.hh
@@ -4,19 +4,96 @@
 #include <stdexcept>
 #include <vereign/bytes/buffer.hh>
 
-// FIXME: add docs
+/**
+ * Binary encoding of integers, bytes.
+ *
+ * The integers are encoded in little endian.
+ */
 namespace vereign::encoding::binary {
 
+/**
+ * Encodes a byte.
+ *
+ * On success the out buffer size will be incremented with 1.
+ *
+ * @param out buffer where the byte will be written.
+ * @param v the byte that will be encoded.
+ */
 void EncodeUint8(bytes::Buffer& out, uint8_t v);
+
+/**
+ * Decodes a byte.
+ *
+ * @param b the source bytes from where the single byte will be decoded.
+ */
 auto DecodeUint8(bytes::View& b) -> uint8_t;
 
+/**
+ * Encodes a uint64.
+ *
+ * The number is encoded in little endian.
+ *
+ * @param out where the value will be written.
+ * @param v the number that will be encoded.
+ */
 void EncodeUint64(uint8_t* out, uint64_t v);
+
+/**
+ * Encodes a uint64.
+ *
+ * On success the out buffer size will be incremented with 8.
+ * The number is encoded in little endian.
+ *
+ * @param out where the value will be written.
+ * @param v the number that will be encoded.
+ */
 void EncodeUint64(bytes::Buffer& out, uint64_t v);
 
+/**
+ * Decodes a uint64.
+ *
+ * The number is expected to be encoded in little endian.
+ *
+ * @param b the source number.
+ * @returns the decoded number.
+ */
 auto DecodeUint64(const uint8_t* b) -> uint64_t;
+
+/**
+ * Decodes a uint64.
+ *
+ * The number is expected to be encoded in little endian.
+ *
+ * @param b the source bytes where the encoded number will be read from.
+ * @returns the decoded number.
+ *
+ * @throws encoding::Error when the source bytes size is less than 8.
+ */
 auto DecodeUint64(bytes::View b) -> uint64_t;
 
+/**
+ * Encodes bytes.
+ *
+ * The result is 8 bytes for the size, followed by the bytes them self.
+ * The out buffer size is incremented with out.Size() + 8.
+ *
+ * @param out the where the encoded value will be written.
+ * @param bytes the source bytes that will be encoded.
+ */
 void EncodeBytes(bytes::Buffer& out, bytes::View bytes);
+
+/**
+ * Decode bytes.
+ *
+ * Note that the decoded bytes reference to the source buffer.
+ * In other words it is only a view, there is no copying involved.
+ *
+ * @param b the source with the encoded bytes.
+ * @param out the decoded bytes.
+ * @returns the bytes read during the decoding (8 + out.Size()).
+ *
+ * @throws encoding::Error when the decoding fails.
+ */
 auto DecodeBytes(bytes::View b, bytes::View& out) -> std::size_t;
 
 } // namespace vereign::encoding::binary
diff --git a/cpp/src/vereign/encoding/errors.hh b/cpp/src/vereign/encoding/errors.hh
new file mode 100644
index 0000000000000000000000000000000000000000..d9d012210165fb645f600dc60a989dfb5ffcb392
--- /dev/null
+++ b/cpp/src/vereign/encoding/errors.hh
@@ -0,0 +1,21 @@
+#ifndef __VEREIGN_ENCODING_ERRORS_HH
+#define __VEREIGN_ENCODING_ERRORS_HH
+
+#include <stdexcept>
+
+namespace vereign::encoding {
+
+/**
+ * Base error for namespace vereign::encoding.
+ */
+class Error : public std::runtime_error {
+public:
+  Error(const std::string& what)
+    : std::runtime_error(what)
+  {
+  }
+};
+
+} // namespace vereign::encoding::binary
+
+#endif // __VEREIGN_ENCODING_ERRORS_HH
diff --git a/cpp/src/vereign/encoding/hex.cc b/cpp/src/vereign/encoding/hex.cc
index e513512bfddecebab9f9fdd51423bbf5ad811f36..46ed21564dbd5e8cc363e43e04123a9c6a5bddaf 100644
--- a/cpp/src/vereign/encoding/hex.cc
+++ b/cpp/src/vereign/encoding/hex.cc
@@ -39,16 +39,16 @@ void Encode(bytes::View src, bytes::Buffer& encoded) {
   encoded.IncSize(src.Size() * 2);
 }
 
-void Decode(std::string_view src, bytes::Buffer& decoded) {
-  if (src.size() == 0) {
+void Decode(bytes::View src, bytes::Buffer& decoded) {
+  if (src.Size() == 0) {
     return ;
   }
 
-  decoded.Reserve(src.size() / 2);
-  for (int i = 0, len = (int) src.size() - 1; i < len; i += 2) {
+  decoded.Reserve(src.Size() / 2);
+  for (int i = 0, len = (int) src.Size() - 1; i < len; i += 2) {
     decoded[i/2] = detail::charToInt(src[i]) * 16 + detail::charToInt(src[i + 1]);
   }
-  decoded.IncSize(src.size() / 2);
+  decoded.IncSize(src.Size() / 2);
 }
 
 void EncodeReverse(bytes::View src, bytes::Buffer& encoded) {
diff --git a/cpp/src/vereign/encoding/hex.hh b/cpp/src/vereign/encoding/hex.hh
index f2e406453bda12dbead8086bcf70099b72d2b176..6dec0f13270bae1d163d7f9114e56276f082515d 100644
--- a/cpp/src/vereign/encoding/hex.hh
+++ b/cpp/src/vereign/encoding/hex.hh
@@ -6,9 +6,52 @@
 
 namespace vereign::encoding::hex {
 
+/**
+ * Encodes bytes in hexadecimal.
+ *
+ * Example:
+ * @code
+ * bytes::Buffer encoded;
+ * encoding::hex::Decode(bytes::View("foobar"), encoded);
+ *
+ * assert(encoded.View().String() == "666f6f626172");
+ * @endcode
+ *
+ * @param src The bytes that will be encoded.
+ * @param encoded The buffer where the encoded bytes will be written.
+ */
 void Encode(bytes::View src, bytes::Buffer& encoded);
+
+/**
+ * Decodes hexadecimal encoded bytes.
+ *
+ * Example:
+ * @code
+ * bytes::Buffer decoded;
+ * encoding::hex::Decode(bytes::View("666f6f626172"), decoded);
+ *
+ * assert(decoded.View().String() == "foobar");
+ * @endcode
+ *
+ * @param src The bytes that will be decoded.
+ * @param encoded The buffer where the decoded bytes will be written.
+ */
 void Decode(bytes::View src, bytes::Buffer& decoded);
 
+/**
+ * Encodes bytes in hexadecimal in reverse order.
+ *
+ * Example:
+ * @code
+ * bytes::Buffer encoded;
+ * encoding::hex::Decode(bytes::View("foobar"), encoded);
+ *
+ * assert(encoded.View().String() == "7261626f6f66");
+ * @endcode
+ *
+ * @param src The bytes that will be encoded.
+ * @param encoded The buffer where the encoded bytes will be written.
+ */
 void EncodeReverse(bytes::View src, bytes::Buffer& encoded);
 
 } // vereign::encoding::hex
diff --git a/cpp/src/vereign/fs/operations.cc b/cpp/src/vereign/fs/operations.cc
index 4ebcad13b8fc38807f5754dae166408add50ea27..9f33ed2d31d3322c2748ee32ba91d4b9c477f53f 100644
--- a/cpp/src/vereign/fs/operations.cc
+++ b/cpp/src/vereign/fs/operations.cc
@@ -18,11 +18,11 @@ auto Exists(const std::string& path) -> bool {
   return f.good();
 }
 
-auto IsDirectory(const std::string& path) -> bool {
+auto IsDir(const std::string& path) -> bool {
   return boost::filesystem::is_directory(fs::detail::StringToPath(path));
 }
 
-auto CreateDirectory(const std::string& path) -> bool {
+auto CreateDir(const std::string& path) -> bool {
   return boost::filesystem::create_directory(fs::detail::StringToPath(path));
 }
 
diff --git a/cpp/src/vereign/fs/operations.hh b/cpp/src/vereign/fs/operations.hh
index 969289657a91ad03d4338b01f502d79d5bc79b49..e5f60c773a1eb9de85d76b32100e0c11d033c5bf 100644
--- a/cpp/src/vereign/fs/operations.hh
+++ b/cpp/src/vereign/fs/operations.hh
@@ -15,14 +15,14 @@ auto Exists(const std::string& path) -> bool;
  *
  * @param path Path to check.
  */
-auto IsDirectory(const std::string& path) -> bool;
+auto IsDir(const std::string& path) -> bool;
 
 /**
  * Creates the last directory of the provided path.
  *
  * @param path Path of the directory that will be created.
  */
-auto CreateDirectory(const std::string& path) -> bool;
+auto CreateDir(const std::string& path) -> bool;
 
 } // namespace vereign::fs
 
diff --git a/cpp/src/vereign/fs/util.cc b/cpp/src/vereign/fs/util.cc
index c59d0c2b1b97236b6c2abef8eaf6c867eadc7d6b..2e39a022f9ecd8d38388467b85beaedb955856d6 100644
--- a/cpp/src/vereign/fs/util.cc
+++ b/cpp/src/vereign/fs/util.cc
@@ -61,9 +61,9 @@ auto TempFilePath(std::string_view prefix) -> std::string {
 }
 
 auto TempDir(std::string_view prefix) -> std::string {
-  auto dir = TempFilePath(detail::PathToString(boost::filesystem::temp_directory_path()), prefix);
+  auto dir = TempFilePath(prefix);
 
-  CreateDirectory(dir);
+  CreateDir(dir);
 
   return dir;
 }
diff --git a/cpp/src/vereign/grpc/error_code.hh b/cpp/src/vereign/grpc/error_code.hh
new file mode 100644
index 0000000000000000000000000000000000000000..a64de2f872558876262883a92dc1bcda635189f4
--- /dev/null
+++ b/cpp/src/vereign/grpc/error_code.hh
@@ -0,0 +1,25 @@
+#ifndef __VEREIGN_GRPC_ERROR_CODES_HH
+#define __VEREIGN_GRPC_ERROR_CODES_HH
+
+#include <cstdint>
+#include <string>
+
+namespace vereign::grpc {
+
+static constexpr const char* ClientErrorStatus = "Vereign Client Library Error";
+
+enum class ErrorCode : uint64_t {
+  ClientError                 = 1000,
+  UnexpectedError             = 1001,
+  DeviceNotRegistered         = 1002,
+  InvalidPinCode              = 1003,
+  InvalidIdentity             = 1004
+};
+
+inline auto ErrorCodeAsString(ErrorCode ec) -> std::string {
+  return std::to_string(uint64_t(ec));
+}
+
+}
+
+#endif // __VEREIGN_GRPC_ERROR_CODES_HH
diff --git a/cpp/src/vereign/grpc/identity_api.hh b/cpp/src/vereign/grpc/identity_api.hh
index 375ba8c6a4fda0f1d2a79073230510506cdde56e..5e40c265d64d4f2ba1146a98cd274361af2bd82d 100644
--- a/cpp/src/vereign/grpc/identity_api.hh
+++ b/cpp/src/vereign/grpc/identity_api.hh
@@ -1,9 +1,12 @@
 #ifndef __VEREIGN_GRPC_IDENTITY_API_HH
 #define __VEREIGN_GRPC_IDENTITY_API_HH
 
-#include "vereign/client_library/common_types.pb.h"
-#include "vereign/client_library/identity_types.pb.h"
 #include <vereign/grpc/gen/identity_api.hh>
+
+#include <vereign/grpc/error_code.hh>
+#include <vereign/kvstore/errors.hh>
+#include <vereign/client_library/common_types.pb.h>
+#include <vereign/client_library/identity_types.pb.h>
 #include <boost/core/ignore_unused.hpp>
 
 namespace vereign::grpc {
@@ -32,14 +35,21 @@ public:
 
     try {
       this->service_->LoginWithNewDevice(req, resp);
+
+    } catch (const kvstore::InvalidPinCodeError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
     } catch (const std::exception& e) {
-      resp->set_code("500");
-      resp->set_status("Internal Service Error");
+      resp->set_code(ErrorCodeAsString(ErrorCode::ClientError));
+      resp->set_status(ClientErrorStatus);
       resp->set_error(e.what());
+
     } catch (...) {
-      resp->set_code("500");
-      resp->set_status("Internal Service Error");
-      resp->set_error("Internal Service Error");
+      resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(ClientErrorStatus);
     }
 
     return ::grpc::Status::OK;
@@ -54,14 +64,31 @@ public:
 
     try {
       this->service_->LoginWithPreviouslyAddedDevice(req, resp);
+
+    } catch (const kvstore::StorageNotInitializedError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::DeviceNotRegistered));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (const kvstore::InvalidPinCodeError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidPinCode));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
+    } catch (const kvstore::InvalidIdentityError& e) {
+      resp->set_code(ErrorCodeAsString(ErrorCode::InvalidIdentity));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(e.what());
+
     } catch (const std::exception& e) {
-      resp->set_code("500");
-      resp->set_status("Internal Service Error");
+      resp->set_code(ErrorCodeAsString(ErrorCode::ClientError));
+      resp->set_status(ClientErrorStatus);
       resp->set_error(e.what());
+
     } catch (...) {
-      resp->set_code("500");
-      resp->set_status("Internal Service Error");
-      resp->set_error("Internal Service Error");
+      resp->set_code(ErrorCodeAsString(ErrorCode::UnexpectedError));
+      resp->set_status(ClientErrorStatus);
+      resp->set_error(ClientErrorStatus);
     }
 
     return ::grpc::Status::OK;
diff --git a/cpp/src/vereign/grpc/server.cc b/cpp/src/vereign/grpc/server.cc
index 8a4b21ad2224b13c7509163c9669e5b52604fc12..f0fae25a176de979bd9274693687a1d1ef6b272f 100644
--- a/cpp/src/vereign/grpc/server.cc
+++ b/cpp/src/vereign/grpc/server.cc
@@ -53,7 +53,7 @@ public:
   {
     if (storage_path == "") {
       storage_path = fs::path::Join(fs::HomePath(), "vereign");
-      fs::CreateDirectory(storage_path);
+      fs::CreateDir(storage_path);
     }
 
     storage_path = fs::path::Join(storage_path, "db");
diff --git a/cpp/src/vereign/kvstore/README.md b/cpp/src/vereign/kvstore/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..62a67dc5f3d413eb11c04a66e68c26bb62cca1ce
--- /dev/null
+++ b/cpp/src/vereign/kvstore/README.md
@@ -0,0 +1,71 @@
+# Vereign C++ Client Library Storage Spec {#vcl_storage_spec}
+
+## Overview
+
+The Vereign C++ Library Storage is used for securely store locally user's identity and profile
+certificates.
+
+The storage is a key/value storage where the values are encrypted with `master key` using
+`AES256-GCM` cypher.
+
+The current backend of the storage is sqlite3, where the database file is normally located at 
+`${HOME}/vereign/db`.
+
+The `master key` key is provided in different way depending on the operating system.
+
+## windows master key
+
+Under windows [CNG](https://docs.microsoft.com/en-us/windows/win32/seccng/about-cng) is used for 
+generating a persistent RSA key pair 2048 bits long. The name of the key in the windows storage by 
+default is `vereign_key`. This key is used to encrypt a randomly generated AES256 key, and the 
+encrypted key is saved in the storage under a special key `__master_key`.
+
+## linux master key
+
+Under linux the user must provide a password (pin code).
+This password along with randomly generated salt is used to derive a master key using
+`PKCS5_PBKDF2_HMAC` with `SHA256` where the salt is 16 bytes and the iterations are 2^18.
+The salt and the iterations are stored in the storage under special keys `__master_key_salt` and
+`master_key_iterations`. Note that these are stored in plain form without encryption.
+
+## locking and storage tag
+
+Every time the storage is going to be modified the whole storage must be locked, which with the 
+sqlite3 backend is achieved with so called exclusive transaction.
+This is needed in order to have consistency when more then a single application use the same storage.
+
+But locking is not enough to achieve consistency. If an application A already had opened the storage,
+and application B reset the identity (changing the master key), and after some time A writes to the
+storage it will overwrite values written by B leaving the storage in a state where different values
+are encrypted with different keys.
+
+In order to prevent this, when the master key is created for the first time a 16 byte long random
+tag is generated and stored encrypted with the master key into a special key `__tag`.
+Then whenever a value must be written after the storage is locked additionally the `__tag` is read
+and decrypted. If the tag retrieval or decryption fails, and error is triggered and the write is
+cancelled.
+
+## encrypted values encoding
+
+As already mentioned the values are encrypted with `AES256-GCM`.
+
+The encrypted values are encoded according to the BNF grammars described below.
+
+The primitives used are:
+* `uint8` - a single byte.
+* `uint64` - unsigned 64 bit integer in Little Endian.
+* `bytes` - a sequence of bytes.
+
+### encoding version 1
+
+```
+Bytes = Size Data
+  Size = uint64
+  Data = bytes
+
+EncryptedValue = Version IV Tag EncryptedData
+  Version = uint8
+  IV = bytes
+  Tag = bytes
+  EncryptedData = bytes
+```
diff --git a/cpp/src/vereign/kvstore/crypto_storage.hh b/cpp/src/vereign/kvstore/crypto_storage.hh
index a258e0ab49081a6b14b087cb24e318567c5fe6e8..671e9abe3d82ba51723c03401a94db82c95d66a7 100644
--- a/cpp/src/vereign/kvstore/crypto_storage.hh
+++ b/cpp/src/vereign/kvstore/crypto_storage.hh
@@ -12,15 +12,95 @@ class CryptoStorageImpl;
 
 }
 
+/**
+ * Crypto storage is used for securely store sensitive user's data.
+ *
+ * This includes user's device private/public key, profile certificates etc.
+ *
+ * The CryptoStorage has different implementations per operating system.
+ * Check detail/linux_crypto_storage.cc and detail/win_crypto_storage.cc.
+ *
+ * For more information about the crypto storage design check the [storage spec](@ref vcl_storage_spec)
+ * in the README.md.
+ *
+ */
 class CryptoStorage {
 public:
+  /**
+   * Creates CryptoStorage.
+   *
+   * @param storage The underlying key/value storage.
+   * @param disable_key_protection Used only under Windows. If false when the master RSA key
+   *    is created and loaded, the user will be asked for his consent by showing a dialog window
+   *    with text information specified in the UI policy.
+   */
   CryptoStorage(Storage& storage, bool disable_key_protection = false);
+
+  /**
+   * Default destructor - does nothing.
+   */
   ~CryptoStorage();
 
+  // disable copying
+  CryptoStorage(const CryptoStorage&) = delete;
+  auto operator=(const CryptoStorage&) -> CryptoStorage& = delete;
+
+  /**
+   * Reset the storage, by effectively initializing a new empty storage.
+   *
+   * Although the resulting storage is empty it will include some internal data needed for the
+   * master key management.
+   *
+   * @param pin Used only under Linux. A pin code that will be used for derivation of the master key.
+   *    Under windows ncrypt is used for generating a RSA key which is used for encryption of the
+   *    master key, and thus the pin is not used.
+   *
+   * @throws Error on failure.
+   */
   void Reset(const std::string& pin);
+
+  /**
+   * Open the storage.
+   *
+   * @param pin Used only under Linux. A pin code that will be used for derivation of the master key.
+   *    Under windows ncrypt is used for generating a RSA key which is used for encryption of the
+   *    master key, and thus the pin is not used.
+   *
+   * @throws StorageNotInitializedError when storage is not initialized yet. One must use
+   *    CryptoStorage::Reset to initialize the storage prior calling CryptoStorage::Open.
+   *
+   * @throws InvalidPinCodeError (only under Linux) when the pin code is empty or does not match
+   *    the pin code used with CryptoStorage::Reset.
+   *
+   * @throws Error when the key derivation under Linux fails.
+   *
+   * @throws InvalidIdentityError when the master key could not be decrypted under Windows.
+   *    This possibly means that the RSA key was changed.
+   */
   void Open(const std::string& pin);
 
+  /**
+   * Encrypt and store bytes value into the storage.
+   *
+   * @param key The key under which the value will be stored.
+   * @param value The bytes that will be encrypted and stored.
+   *
+   * @throws StorageNotInitializedError when the storage is not initialized.
+   * @throws InvalidIdentityError when another application had reset the storage.
+   */
   void PutBytes(const std::string& key, bytes::View value);
+
+  /**
+   * Retrieve and decrypt bytes value from the storage.
+   *
+   * @param key The key of the value that will be retrieved.
+   * @param value Buffer where the value will be returned.
+   *
+   * @throws StorageNotInitializedError when the storage is not initialized.
+   * @throws ValueNotFoundError when there is no value under the provided key.
+   * @throws encoding::Error when the encrypted value cannot be decoded.
+   * @throws crypto::OpenSSLError when the value cannot be decrypted.
+   */
   void GetBytes(const std::string& key, bytes::Buffer& value);
 
 private:
diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
index 940ccb5016ff1e780872451331847df63a8acab2..83bb4c7b1930b76829af0192b6357b674a42f7ae 100644
--- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.cc
@@ -31,14 +31,14 @@ BaseCryptoStorageImpl::BaseCryptoStorageImpl(kvstore::Storage& storage)
 
 void BaseCryptoStorageImpl::encryptBytes(const std::string& key, bytes::View value) {
   if (key_.Size() == 0) {
-    throw Error("crypto storage is not initialized");
+    throw StorageNotInitializedError{"key not initialized"};
   }
 
   bytes::Buffer iv;
   bytes::Buffer tag;
   bytes::Buffer encrypted;
 
-  crypto::aes::GCM256Encrypt(value, key_.View(), iv, encrypted, tag);
+  crypto::aes::GCM256Encrypt(value, key_.View(), iv, tag, encrypted);
 
   bytes::Buffer encoded_value;
   EncodeEncryptedValue(encoded_value, iv.View(), tag.View(), encrypted.View());
@@ -49,7 +49,9 @@ void BaseCryptoStorageImpl::encryptBytes(const std::string& key, bytes::View val
 void BaseCryptoStorageImpl::PutBytes(const std::string& key, bytes::View value) {
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
-  validateTag();
+  if (!isTagValid()) {
+    throw InvalidIdentityError{};
+  }
 
   encryptBytes(key, value);
 }
@@ -58,7 +60,7 @@ void BaseCryptoStorageImpl::GetBytes(const std::string& key, bytes::Buffer& valu
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
   if (key_.Size() == 0) {
-    throw Error("crypto storage is not initialized");
+    throw StorageNotInitializedError{"key not initialized"};
   }
 
   bytes::Buffer encoded;
@@ -76,18 +78,19 @@ void BaseCryptoStorageImpl::initKey(bytes::Buffer&& key) {
   key_ = std::move(key);
 }
 
-void BaseCryptoStorageImpl::validateTag() const {
+auto BaseCryptoStorageImpl::isTagValid() const -> bool {
   bytes::Buffer tag;
   try {
     GetBytes("__tag", tag);
-  } catch (const crypto::Error& err) {
-    throw IdentityChanged{};
+
+    return true;
+  } catch (const crypto::Error&) {
+    return false;
   }
 }
 
 void BaseCryptoStorageImpl::updateTag() {
-  bytes::Buffer tag{tagSizeBytes};
-  crypto::Rand(tag);
+  auto tag = crypto::Rand(tagSizeBytes);
 
   encryptBytes("__tag", tag.View());
 }
diff --git a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
index 2351c822b6f0782027b12f3f2ff53f38ea9cb50a..49278c3f6847d4555dfd3ba58fc52a38a7d05e07 100644
--- a/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
+++ b/cpp/src/vereign/kvstore/detail/base_crypto_storage.hh
@@ -18,7 +18,7 @@ public:
 
 protected:
   void initKey(bytes::Buffer&& key);
-  void validateTag() const;
+  auto isTagValid() const -> bool;
   void updateTag();
 
 private:
diff --git a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
index 03a6c26c3b8dba1e79492dec86c937aa8bc5901d..6a19e57500e6b570ae2ce181891618351ec335b4 100644
--- a/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/linux_crypto_storage.cc
@@ -33,14 +33,25 @@ CryptoStorageImpl::CryptoStorageImpl(kvstore::Storage& storage, bool disable_key
 }
 
 void CryptoStorageImpl::Open(const std::string& pin) {
+  if (pin.empty()) {
+    throw InvalidPinCodeError{};
+  }
+
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
   bytes::Buffer salt;
-  auto iterations = storage_.GetInt64("__master_key_iterations");
+
+  int64_t iterations = 0;
+  try {
+    iterations = storage_.GetInt64("__master_key_iterations");
+    storage_.GetBytes("__master_key_salt", salt);
+  } catch (const std::exception& e) {
+    throw StorageNotInitializedError{e.what()};
+  }
+
   if (iterations == 0) {
-    throw StorageNotInitializedError{};
+    throw StorageNotInitializedError{"iterations cannot be zero"};
   }
-  storage_.GetBytes("__master_key_salt", salt);
 
   bytes::Buffer key{aesKeySizeBytes};
 
@@ -51,7 +62,7 @@ void CryptoStorageImpl::Open(const std::string& pin) {
     salt.View().Size(),
     iterations,
     EVP_sha256(),
-    key.FreeCap(),
+    aesKeySizeBytes,
     key.end()
   );
   if (result == 0) {
@@ -61,13 +72,17 @@ void CryptoStorageImpl::Open(const std::string& pin) {
   key.IncSize(aesKeySizeBytes);
 
   initKey(std::move(key));
-  // FIXME: write tests for tampering and regular identity change
-  validateTag();
+  if (!isTagValid()) {
+    throw InvalidPinCodeError{};
+  }
 }
 
 void CryptoStorageImpl::Reset(const std::string& pin) {
-  bytes::Buffer salt{saltSizeBytes};
-  crypto::Rand(salt);
+  if (pin.empty()) {
+    throw InvalidPinCodeError{};
+  }
+
+  auto salt = crypto::Rand(saltSizeBytes);
 
   bytes::Buffer key{aesKeySizeBytes};
 
@@ -78,7 +93,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) {
     salt.View().Size(),
     iterations,
     EVP_sha256(),
-    key.FreeCap(),
+    aesKeySizeBytes,
     key.end()
   );
   if (result == 0) {
diff --git a/cpp/src/vereign/kvstore/detail/value_encoder.cc b/cpp/src/vereign/kvstore/detail/value_encoder.cc
new file mode 100644
index 0000000000000000000000000000000000000000..6144bebbdffdee83f9797cf063d9729b34d96c35
--- /dev/null
+++ b/cpp/src/vereign/kvstore/detail/value_encoder.cc
@@ -0,0 +1,42 @@
+#include <vereign/kvstore/detail/value_encoder.hh>
+
+#include <vereign/kvstore/errors.hh>
+#include <vereign/encoding/binary.hh>
+
+namespace vereign::kvstore::detail {
+
+void EncodeEncryptedValue(
+  bytes::Buffer& out,
+  bytes::View iv,
+  bytes::View tag,
+  bytes::View encrypted
+) {
+  // 25 = buffers size numbers (3 int64) plus 1 byte for the version
+  out.Reserve(iv.Size() + tag.Size() + encrypted.Size() + 25);
+  // encode version
+  encoding::binary::EncodeUint8(out, 1);
+
+  // encode data
+  encoding::binary::EncodeBytes(out, iv);
+  encoding::binary::EncodeBytes(out, tag);
+  encoding::binary::EncodeBytes(out, encrypted);
+}
+
+void DecodeEncryptedValue(
+  bytes::View encoded,
+  bytes::View& iv,
+  bytes::View& tag,
+  bytes::View& encrypted
+) {
+  auto version = encoding::binary::DecodeUint8(encoded);
+  if (version != 1) {
+    throw Error("decoding encrypted value failed: invalid version");
+  }
+
+  auto offset = 1;
+  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), iv);
+  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), tag);
+  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), encrypted);
+}
+
+} // namespace vereign::kvstore::detail
diff --git a/cpp/src/vereign/kvstore/detail/value_encoder.hh b/cpp/src/vereign/kvstore/detail/value_encoder.hh
index 101b77fa08f04c73ecf940239625c6c2de8c5c71..3c9e532853a853febaadf44b39c7941c1f0f28e1 100644
--- a/cpp/src/vereign/kvstore/detail/value_encoder.hh
+++ b/cpp/src/vereign/kvstore/detail/value_encoder.hh
@@ -3,46 +3,43 @@
 
 #include <vereign/bytes/view.hh>
 #include <vereign/bytes/buffer.hh>
-#include <vereign/encoding/binary.hh>
-
-#include <vereign/kvstore/errors.hh>
 
 namespace vereign::kvstore::detail {
 
-// FIXME: move to implementation file
-inline void EncodeEncryptedValue(
+/**
+ * Encodes value encrypted with AES in GCM mode.
+ *
+ * @param out The encoded result.
+ * @param iv The IV of the encrypted value.
+ * @param tag The tag of the encrypted value.
+ * @param encrypted The encrypted bytes.
+ */
+void EncodeEncryptedValue(
   bytes::Buffer& out,
   bytes::View iv,
   bytes::View tag,
   bytes::View encrypted
-) {
-  // 25 = buffers size numbers (3 int64) plus 1 byte for the version
-  out.Reserve(iv.Size() + tag.Size() + encrypted.Size() + 25);
-  // encode version
-  encoding::binary::EncodeUint8(out, 1);
-
-  // encode data
-  encoding::binary::EncodeBytes(out, iv);
-  encoding::binary::EncodeBytes(out, tag);
-  encoding::binary::EncodeBytes(out, encrypted);
-}
-
-inline void DecodeEncryptedValue(
+);
+
+/**
+ * Decode value encrypted with AES in GCM mode.
+ *
+ * Note that iv, tag and encrypted are decoded in-place, meaning that they point to the same memory
+ * of the `encoded` input.
+ *
+ * @param out The encoded value.
+ * @param iv The decoded IV of the encrypted value.
+ * @param tag The decoded tag of the encrypted value.
+ * @param encrypted The decoded encrypted bytes.
+ *
+ * @throws kvstore::Error on failure.
+ */
+void DecodeEncryptedValue(
   bytes::View encoded,
   bytes::View& iv,
   bytes::View& tag,
   bytes::View& encrypted
-) {
-  auto version = encoding::binary::DecodeUint8(encoded);
-  if (version != 1) {
-    throw Error("decoding encrypted value failed: invalid version");
-  }
-
-  auto offset = 1;
-  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), iv);
-  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), tag);
-  offset += encoding::binary::DecodeBytes(encoded.Slice(offset), encrypted);
-}
+);
 
 } // namespace vereign::kvstore::detail
 
diff --git a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
index 271acf4bdb6f12db5af2fb80a86e9cb6ddd258ae..f3090c6687a4b40ae5f57fde6981bbdc4243cbf6 100644
--- a/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
+++ b/cpp/src/vereign/kvstore/detail/win_crypto_storage.cc
@@ -13,6 +13,7 @@
 
 #include <boost/core/ignore_unused.hpp>
 #include <vereign/ncrypt/rsa.hh>
+#include <vereign/ncrypt/errors.hh>
 
 #include <chrono>
 
@@ -25,7 +26,6 @@ namespace {
   constexpr auto lockRetrySleep = std::chrono::milliseconds{1000};
 
   // FIXME: ask business for these values
-  constexpr const auto vereignKey = std::string_view{"vereign_key"};
   constexpr const auto vereignKeyCreationTitle = std::string_view{"Vereign Client"};
   constexpr const auto vereignKeyDescription = std::string_view{
     "Vereign Client will use this key to authenticate with the Vereign Services"
@@ -45,20 +45,26 @@ void CryptoStorageImpl::Open(const std::string& pin) {
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
   auto provider = ncrypt::rsa::OpenStorageProvider();
-  auto rsa_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(vereignKey));
+  auto rsa_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(VereignKeyName));
   if (!rsa_key) {
-    throw IdentityChanged{};
+    throw StorageNotInitializedError{"key not initialized"};
   }
 
-  bytes::Buffer encrypted_key;
-  storage_.GetBytes("__master_key", encrypted_key);
-
   bytes::Buffer key;
-  ncrypt::rsa::PrivateKeyDecrypt(rsa_key.Get(), encrypted_key.View(), key);
+  try {
+    bytes::Buffer encrypted_key;
+    storage_.GetBytes("__master_key", encrypted_key);
+
+    ncrypt::rsa::PrivateKeyDecrypt(rsa_key.Get(), encrypted_key.View(), key);
+  } catch(const std::exception&) {
+    throw InvalidIdentityError{};
+  }
 
   initKey(std::move(key));
   // FIXME: write tests for tampering and regular identity change
-  validateTag();
+  if (!isTagValid()) {
+    throw InvalidIdentityError{};
+  }
 }
 
 void CryptoStorageImpl::Reset(const std::string& pin) {
@@ -66,7 +72,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) {
   kvstore::Lock l{storage_, lockRetryCount, lockRetrySleep};
 
   auto provider = ncrypt::rsa::OpenStorageProvider();
-  auto old_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(vereignKey));
+  auto old_key = ncrypt::rsa::LoadKey(provider.Get(), std::string(VereignKeyName));
   if (old_key) {
     ncrypt::rsa::DeleteKey(old_key.Get());
   }
@@ -83,7 +89,7 @@ void CryptoStorageImpl::Reset(const std::string& pin) {
   auto rsa_key = ncrypt::rsa::CreateKey(
     provider.Get(),
     keySizeBits,
-    std::string(vereignKey),
+    std::string(VereignKeyName),
     ui_policy
   );
 
diff --git a/cpp/src/vereign/kvstore/errors.hh b/cpp/src/vereign/kvstore/errors.hh
index 371f273b1257ba5a464f2d03737a8bb86536707e..c119c510273cce58d67071e56da2ad16f43983da 100644
--- a/cpp/src/vereign/kvstore/errors.hh
+++ b/cpp/src/vereign/kvstore/errors.hh
@@ -13,10 +13,18 @@ public:
   }
 };
 
+class ValueNotFoundError : public Error {
+public:
+  ValueNotFoundError()
+    : Error{"value not found"}
+  {
+  }
+};
+
 class StorageNotInitializedError : public Error {
 public:
-  StorageNotInitializedError()
-    : Error{"storage is not initialized"}
+  StorageNotInitializedError(const std::string& reason)
+    : Error{"storage is not initialized: " + reason}
   {
   }
 };
@@ -29,10 +37,18 @@ public:
   }
 };
 
-class IdentityChanged : public Error {
+class InvalidPinCodeError : public Error {
+public:
+  InvalidPinCodeError()
+    : Error{"invalid pin code"}
+  {
+  }
+};
+
+class InvalidIdentityError : public Error {
 public:
-  IdentityChanged()
-    : Error{"identity has been changed"}
+  InvalidIdentityError()
+    : Error{"invalid identity"}
   {
   }
 };
diff --git a/cpp/src/vereign/kvstore/lock.cc b/cpp/src/vereign/kvstore/lock.cc
index 65935c0c3583dfcba3cb52bd11b2c204d4f48056..5a0863e967ea6de6243652a7bbb1752c8a286480 100644
--- a/cpp/src/vereign/kvstore/lock.cc
+++ b/cpp/src/vereign/kvstore/lock.cc
@@ -21,7 +21,7 @@ Lock::Lock(Storage& storage, int retry_count, std::chrono::milliseconds sleep_in
       storage.Lock();
 
       return;
-    } catch (const LockError& err) {
+    } catch (const LockError&) {
       std::this_thread::sleep_for(sleep_interval);
 
       continue;
diff --git a/cpp/src/vereign/kvstore/sqlite_storage.cc b/cpp/src/vereign/kvstore/sqlite_storage.cc
index 3e45d0f5f6bd74806b7ad2a1a5f49d9c9e765a5e..4789346fef43cde99a8701c620d2d93eea7f0fb7 100644
--- a/cpp/src/vereign/kvstore/sqlite_storage.cc
+++ b/cpp/src/vereign/kvstore/sqlite_storage.cc
@@ -90,9 +90,11 @@ void SqliteStorage::GetBytes(const std::string& key, bytes::Buffer& value) {
   stmt.BindText(1, key);
   auto end = stmt.Step();
 
-  if (!end) {
-    value.Write(stmt.GetColumnBlob(0));
+  if (end) {
+    throw ValueNotFoundError{};
   }
+
+  value.Write(stmt.GetColumnBlob(0));
 }
 
 void SqliteStorage::PutInt64(const std::string& key, int64_t value) {
@@ -115,7 +117,7 @@ auto SqliteStorage::GetInt64(const std::string& key) -> int64_t {
   auto end = stmt.Step();
 
   if (end) {
-    return 0;
+    throw ValueNotFoundError{};
   }
 
   auto buf = stmt.GetColumnBlob(0);
diff --git a/cpp/src/vereign/kvstore/storage.hh b/cpp/src/vereign/kvstore/storage.hh
index aa21c54684d0aef25313459f9b0f0194847beff1..a190bd54fc573ff0512c80e10f7283cbd88e9542 100644
--- a/cpp/src/vereign/kvstore/storage.hh
+++ b/cpp/src/vereign/kvstore/storage.hh
@@ -5,6 +5,8 @@
 
 namespace vereign::kvstore {
 
+constexpr const auto VereignKeyName = std::string_view{"vereign_key"};
+
 class Storage {
 public:
   virtual void Lock() = 0;
diff --git a/cpp/src/vereign/ncrypt/errors.hh b/cpp/src/vereign/ncrypt/errors.hh
index a37056c02947ccdcd0aa49841aaeb4593dd6c06e..fe6920132b3e3cc1f1596c68af9671c3cfa2764f 100644
--- a/cpp/src/vereign/ncrypt/errors.hh
+++ b/cpp/src/vereign/ncrypt/errors.hh
@@ -7,14 +7,37 @@
 
 namespace vereign::ncrypt {
 
+// windows returns this error when there is unexpected NULL input
 constexpr SECURITY_STATUS NTE_NULL_REFERENCE_POINTER = 0x800706f4;
 
+/**
+ * Returns a string representation of the SECURITY_STATUS errors.
+ *
+ * @param status The SECURITY_STATUS error.
+ * @returns a string representation of the SECURITY_STATUS error.
+ */
 auto SecurityStatusToString(SECURITY_STATUS status) -> std::string;
 
+/**
+ * The base error type for the namespace vereign::ncrypto.
+ */
 class Error : public std::runtime_error {
 public:
+  /**
+   * Creates Error with status and additional message.
+   *
+   * Example:
+   * @code
+   * auto err = Error{NTE_FAIL, "operation failed"};
+   * assert(err.what() == "operation failed: NTE_FAIL");
+   * assert(err.SecurityStatus() == NTE_FAIL);
+   * @endcode
+   */
   Error(SECURITY_STATUS status, const std::string& msg);
 
+  /**
+   * Returns the SECURITY_STATUS associated with this error.
+   */
   auto SecurityStatus() const -> SECURITY_STATUS;
 
 private:
diff --git a/cpp/src/vereign/ncrypt/rsa.hh b/cpp/src/vereign/ncrypt/rsa.hh
index 6cb4048d401b997f6f4f8ba79b38d5c9eab8581c..06d3f05d2cb1892b6a43956c6e6940f6d6b3d0a0 100644
--- a/cpp/src/vereign/ncrypt/rsa.hh
+++ b/cpp/src/vereign/ncrypt/rsa.hh
@@ -11,23 +11,138 @@
 
 namespace vereign::ncrypt::rsa {
 
+/**
+ * KeyUIPolicy is used to specify some UI captions shown to the user in a dialog when a key is
+ * created or opened for the first time in a process.
+ */
 struct KeyUIPolicy {
   std::string_view CreationTitle;
   std::string_view Description;
   std::string_view FriendlyName;
 };
 
+/**
+ * Opens the default ncrypt storage provider.
+ *
+ * @returns the storage provider handle.
+ *
+ * @throws ncrypt::Error on failure.
+ */
 auto OpenStorageProvider() -> UniquePtr;
+
+/**
+ * Loads a key from a ncrypt storage provider.
+ *
+ * Example:
+ * @code
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::LoadKey(provider.Get(), "test_key");
+ * assert(key.Get() != 0);
+ * @endcode
+ *
+ * @param provider The ncrypt storage provider.
+ * @param key_name The name of the key that will be loaded.
+ * @returns the loaded key if the key exists, or null ncrypt::UniquePtr if the key does not exists.
+ *
+ * @throws ncrypt::Error on failure.
+ */
 auto LoadKey(NCRYPT_PROV_HANDLE provider, const std::string& key_name) -> UniquePtr;
+
+/**
+ * Creates persistent RSA key into a given storage provider.
+ *
+ * Example:
+ * @code
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * // create key without UI policy
+ * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {});
+ * assert(key.Get() != 0);
+ * @endcode
+ *
+ * @param provider The ncrypt storage provider.
+ * @param bits The length of the key in bits.
+ * @param key_name The new key name.
+ * @param ui_policy The UI policy. If the UI policy is not provided, the key will not have any
+ *    protection. If the UI policy is provided when the key is created and loaded the user will be
+ *    asked for his consent by showing a dialog window with text information specified in the UI policy.
+ *
+ * @returns the created key on success.
+ *
+ * @throws ncrypt::Error on failure.
+ */
 auto CreateKey(
   NCRYPT_PROV_HANDLE provider,
   int bits,
   const std::string& key_name,
   std::optional<KeyUIPolicy> ui_policy
 ) -> UniquePtr;
+
+/**
+ * Deletes a key.
+ *
+ * Example:
+ * @code
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::LoadKey(provider.Get(), "test_key");
+ * if (key) {
+ *   ncryp::rsa::DeleteKey(key.Get());
+ * }
+ * @endcode
+ *
+ * @param key The key to delete.
+ *
+ * @throws ncrypt::Error on failure.
+ */
 void DeleteKey(NCRYPT_KEY_HANDLE key);
 
+/**
+ * Encrypts given bytes with RSA public key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {});
+ *
+ * bytes::Buffer encrypted;
+ * ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted);
+ * assert(decrypted.View() == encrypted.View());
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be encrypted.
+ * @param encrypted The result of the encryption.
+ *
+ * @throws ncrypt::Error on failure.
+ */
 void PublicKeyEncrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& encrypted);
+
+/**
+ * Decrypts given bytes with RSA private key.
+ *
+ * Example:
+ * @code
+ * const std::string input{"foo bar"};
+ * auto provider = ncrypt::rsa::OpenStorageProvider();
+ * auto key = ncrypt::rsa::CreateKey(provider.Get(), 2048, "test_key", {});
+ *
+ * bytes::Buffer encrypted;
+ * ncrypt::rsa::PublicKeyEncrypt(key.Get(), bytes::View(input), encrypted);
+ *
+ * bytes::Buffer decrypted;
+ * ncrypt::rsa::PrivateKeyDecrypt(key.Get(), encrypted.View(), decrypted);
+ * assert(decrypted.View() == encrypted.View());
+ * @endcode
+ *
+ * @param key The RSA key.
+ * @param src The bytes that will be decrypted.
+ * @param decrypted The result of the decryption.
+ *
+ * @throws ncrypt::Error on failure.
+ */
 void PrivateKeyDecrypt(NCRYPT_KEY_HANDLE key, bytes::View src, bytes::Buffer& decrypted);
 
 } // vereign::ncrypt::rsa
diff --git a/cpp/src/vereign/ncrypt/unique_ptr.cc b/cpp/src/vereign/ncrypt/unique_ptr.cc
index 06e7d2c9239e9b68b4877b7b78ff8621ef897837..a177839f4ec6c2a12780a5e204db45e92965ecd1 100644
--- a/cpp/src/vereign/ncrypt/unique_ptr.cc
+++ b/cpp/src/vereign/ncrypt/unique_ptr.cc
@@ -11,8 +11,8 @@ UniquePtr::UniquePtr() noexcept
 {
 }
 
-UniquePtr::UniquePtr(ULONG_PTR ptr) noexcept
-  : ptr_{ptr}
+UniquePtr::UniquePtr(ULONG_PTR handle) noexcept
+  : ptr_{handle}
 {
 }
 
diff --git a/cpp/src/vereign/ncrypt/unique_ptr.hh b/cpp/src/vereign/ncrypt/unique_ptr.hh
index 8341e3b08bbd08848a06c0931067ac23848e03e1..ae4475300c7399f136810082988e4c19d3495666 100644
--- a/cpp/src/vereign/ncrypt/unique_ptr.hh
+++ b/cpp/src/vereign/ncrypt/unique_ptr.hh
@@ -5,23 +5,88 @@
 
 namespace vereign::ncrypt {
 
+/**
+ * Holds ownership of ncrypt handles like NCRYPT_PROV_HANDLE, NCRYPT_PROV_HANDLE.
+ *
+ * The UniquePtr should be used any time a ncrypt API returns a handle in order to ensure exception
+ * safety and resource cleanup.
+ *
+ * The owned handler is typically cleaned up in the UniquePtr::~UniquePtr() by using
+ * NCryptFreeObject function.
+ */
 class UniquePtr {
 public:
+  /**
+   * Constructs a default empty object.
+   */
   UniquePtr() noexcept;
-  explicit UniquePtr(ULONG_PTR ptr) noexcept;
 
+  /**
+   * Constructs a UniquePtr that becomes an owner of the passed handle.
+   *
+   * @param handle The handle that becomes owned by the crated UniquePtr.
+   */
+  explicit UniquePtr(ULONG_PTR handle) noexcept;
+
+  /**
+   * Move constructor.
+   */
   UniquePtr(UniquePtr&& other) noexcept;
+
+  /**
+   * Move assignment operator.
+   */
   auto operator=(UniquePtr&& other) noexcept -> UniquePtr&;
 
+  // Disable copying
   UniquePtr(const UniquePtr&) = delete;
   auto operator=(const UniquePtr&) -> UniquePtr&  = delete;
 
-  ~UniquePtr() noexcept ;
+  /**
+   * Upon destruction the owned handle if any is freed with NCryptFreeObject function.
+   */
+  ~UniquePtr() noexcept;
 
+  /**
+   * Retrieve the owned handle.
+   */
   auto Get() const noexcept -> ULONG_PTR;
+
+  /**
+   * Retrieve a pointer to the owned handle.
+   *
+   * Example:
+   * @code
+   * UniquePtr key{};
+   * NCryptOpenKey(provider, key.Ref(), L"test_key", 0, 0);
+   * assert(bool(key) == true);
+   * @endcode
+   *
+   * This makes it possible for a ncrypt API to inject its output handle directly into the
+   * UniquePtr object.
+   */
   auto Ref() noexcept -> ULONG_PTR*;
+
+  /**
+   * Free the owned handler.
+   *
+   * If the owned handler is not null, it frees the handle by calling NCryptFreeObject, and sets
+   * the owned handler to null.
+   */
   void Reset();
+
+  /**
+   * Releases the ownership of the owned handler.
+   *
+   * @returns the owned handler, before releasing the ownership.
+   */
   auto Release() -> ULONG_PTR;
+
+  /**
+   * Checks if *this owns a non-null handler.
+   *
+   * @returns true if *this owns a handler, false otherwise.
+   */
   operator bool() const noexcept;
 
 private:
diff --git a/cpp/tests/integration/init_integration_test_storage.cc b/cpp/tests/integration/init_integration_test_storage.cc
index 32a3fe0063b8a1dd357579d5452ac8cfa1917138..ff234034cd50f576d43b70263a48f724013b4292 100644
--- a/cpp/tests/integration/init_integration_test_storage.cc
+++ b/cpp/tests/integration/init_integration_test_storage.cc
@@ -28,7 +28,7 @@ auto main(int argc, char** argv) -> int {
     storage_path = fs::path::Join(fs::HomePath(), "vereign_integration_test");
   }
 
-  fs::CreateDirectory(storage_path);
+  fs::CreateDir(storage_path);
 
   test::PrepareNewDevice(host, port, public_key, pin, storage_path);
 }
diff --git a/cpp/tests/vereign/CMakeLists.txt b/cpp/tests/vereign/CMakeLists.txt
index 3994b8bad658abf6c3d90ca345bf4f88ed7a9964..0bcfbc6e919333d7cae2525229b4c53b0cf21247 100644
--- a/cpp/tests/vereign/CMakeLists.txt
+++ b/cpp/tests/vereign/CMakeLists.txt
@@ -20,6 +20,10 @@ list(APPEND TESTS_SRC
   sync/channel_test.cc
 
   encoding/base64_test.cc
+  encoding/hex_test.cc
+
+  bytes/buffer_test.cc
+  bytes/view_test.cc
 
   crypto/aes_test.cc
   crypto/rsa_test.cc
@@ -39,6 +43,7 @@ list(APPEND TESTS_SRC
 
   grpc/server_test.cc
   grpc/json/encoder_test.cc
+  grpc/identity_api_test.cc
 )
 
 if (WIN32)
diff --git a/cpp/tests/vereign/bytes/buffer_test.cc b/cpp/tests/vereign/bytes/buffer_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..710c3c184d2111aae21dc02c9b29a9939850842f
--- /dev/null
+++ b/cpp/tests/vereign/bytes/buffer_test.cc
@@ -0,0 +1,491 @@
+#include <vereign/bytes/buffer.hh>
+
+#include <vereign/bytes/errors.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("bytes::Buffer::Buffer", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 0);
+    CHECK(buf.FreeCap() == 0);
+  }
+
+  SECTION("buffer with predefined capacity") {
+    auto buf = bytes::Buffer{16};
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 16);
+    CHECK(buf.FreeCap() == 16);
+  }
+
+  SECTION("buffer from a view") {
+    auto buf = bytes::Buffer{bytes::View{"foo bar"}};
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.Cap() == 7);
+    CHECK(buf.FreeCap() == 0);
+    CHECK(buf.View().String() == "foo bar");
+  }
+
+  SECTION("move construct") {
+    auto old_buf = bytes::Buffer{bytes::View{"foo bar"}};
+    auto buf = std::move(old_buf);
+
+    CHECK(old_buf.Size() == 0);
+    CHECK(old_buf.Cap() == 0);
+    CHECK(old_buf.FreeCap() == 0);
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.Cap() == 7);
+    CHECK(buf.FreeCap() == 0);
+    CHECK(buf.View().String() == "foo bar");
+
+    auto empty = bytes::Buffer{};
+    auto new_empty = std::move(empty);
+
+    CHECK(empty.Size() == 0);
+    CHECK(empty.Cap() == 0);
+    CHECK(empty.FreeCap() == 0);
+
+    CHECK(new_empty.Size() == 0);
+    CHECK(new_empty.Cap() == 0);
+    CHECK(new_empty.FreeCap() == 0);
+  }
+
+  SECTION("move assign") {
+    auto foo = bytes::Buffer{bytes::View{"foo"}};
+    auto bar = bytes::Buffer{bytes::View{"bar"}};
+
+    foo = std::move(bar);
+
+    CHECK(foo.Size() == 3);
+    CHECK(foo.Cap() == 3);
+    CHECK(foo.FreeCap() == 0);
+    CHECK(foo.View().String() == "bar");
+
+    CHECK(bar.Size() == 3);
+    CHECK(bar.Cap() == 3);
+    CHECK(bar.FreeCap() == 0);
+    CHECK(bar.View().String() == "foo");
+
+    auto empty = bytes::Buffer{};
+    foo = std::move(empty);
+
+    CHECK(foo.Size() == 0);
+    CHECK(foo.Cap() == 0);
+    CHECK(foo.FreeCap() == 0);
+    CHECK(foo.View().String() == "");
+
+    CHECK(empty.Size() == 3);
+    CHECK(empty.Cap() == 3);
+    CHECK(empty.FreeCap() == 0);
+    CHECK(empty.View().String() == "bar");
+
+    empty = std::move(foo);
+
+    CHECK(empty.Size() == 0);
+    CHECK(empty.Cap() == 0);
+    CHECK(empty.FreeCap() == 0);
+    CHECK(empty.View().String() == "");
+
+    CHECK(foo.Size() == 3);
+    CHECK(foo.Cap() == 3);
+    CHECK(foo.FreeCap() == 0);
+    CHECK(foo.View().String() == "bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::begin", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.begin() == nullptr);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo bar")};
+
+    CHECK(buf.begin() != nullptr);
+
+    std::string s;
+    for (auto& it : buf) {
+      s += it;
+    }
+
+    CHECK(s == "foo bar");
+
+    for (auto& it : buf) {
+      it = 'x';
+    }
+
+    CHECK(buf.View().String() == "xxxxxxx");
+  }
+}
+
+TEST_CASE("bytes::Buffer::end", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.end() == nullptr);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo bar")};
+
+    CHECK(buf.end() != nullptr);
+
+    std::string s;
+    for (const auto& it : buf) {
+      s += it;
+    }
+
+    CHECK(s == "foo bar");
+
+    for (auto& it : buf) {
+      it = 'x';
+    }
+
+    CHECK(buf.View().String() == "xxxxxxx");
+  }
+
+  SECTION("append to the end") {
+    auto buf = bytes::Buffer{bytes::View("foo bar")};
+
+    buf.Reserve(4);
+    std::strncpy((char*)buf.end(), " baz", 4);
+    buf.IncSize(4);
+
+    CHECK(buf.Size() == 11);
+    CHECK(buf.View().String() == "foo bar baz");
+  }
+}
+
+TEST_CASE("bytes::Buffer::operator[]", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK_THROWS_AS(
+      buf[0],
+      bytes::IndexOutOfBounds
+    );
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+    buf.Reserve(16);
+
+    CHECK(buf.Cap() == 19);
+    CHECK(buf[0] == 'f');
+    CHECK(buf[1] == 'o');
+    CHECK(buf[2] == 'o');
+
+    CHECK_THROWS_AS(
+      buf[19],
+      bytes::IndexOutOfBounds
+    );
+  }
+
+  SECTION("modify buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    buf[0] = 'b';
+    buf[1] = 'a';
+    buf[2] = 'r';
+
+    CHECK_THROWS_AS(
+      buf[3] = 'x',
+      bytes::IndexOutOfBounds
+    );
+
+    CHECK(buf.View().String() == "bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::Size", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.Size() == 0);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Size() == 3);
+  }
+
+  SECTION("append to buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+    buf.Write(bytes::View(" bar"));
+
+    CHECK(buf.Size() == 7);
+  }
+}
+
+TEST_CASE("bytes::Buffer::Cap", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.Cap() == 0);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Cap() == 3);
+  }
+
+  SECTION("append to buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    // capacity is doubled
+    buf.Write(bytes::View("b"));
+
+    CHECK(buf.Cap() == 6);
+  }
+}
+
+TEST_CASE("bytes::Buffer::FreeCap", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    CHECK(buf.FreeCap() == 0);
+  }
+
+  SECTION("non empty buffer") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Cap() == 3);
+    CHECK(buf.FreeCap() == 0);
+
+    buf.Reserve(1);
+
+    CHECK(buf.Cap() - buf.Size() == buf.FreeCap());
+  }
+}
+
+TEST_CASE("bytes::Buffer::Reserve", "[vereign/bytes]") {
+  SECTION("empty buffer") {
+    auto buf = bytes::Buffer{};
+
+    buf.Reserve(8);
+    CHECK(buf.Cap() == 8);
+    CHECK(buf.Size() == 0);
+  }
+
+  SECTION("when the required reservation is less than the old capacity*2, it reserves 2*capacity") {
+    {
+      auto buf = bytes::Buffer{bytes::View("foo")};
+
+      CHECK(buf.Cap() == 3);
+      CHECK(buf.Size() == 3);
+
+      // capacity is doubled
+      buf.Reserve(1);
+
+      CHECK(buf.Cap() == 6);
+      CHECK(buf.FreeCap() == 3);
+      CHECK(buf.Size() == 3);
+    }
+
+    {
+      auto buf = bytes::Buffer{bytes::View("foo")};
+
+      CHECK(buf.Cap() == 3);
+      CHECK(buf.Size() == 3);
+
+      // capacity is doubled
+      buf.Reserve(2);
+
+      CHECK(buf.Cap() == 6);
+      CHECK(buf.FreeCap() == 3);
+      CHECK(buf.Size() == 3);
+    }
+
+    {
+      auto buf = bytes::Buffer{bytes::View("foo")};
+
+      CHECK(buf.Cap() == 3);
+      CHECK(buf.Size() == 3);
+
+      // capacity is doubled
+      buf.Reserve(3);
+
+      CHECK(buf.Cap() == 6);
+      CHECK(buf.FreeCap() == 3);
+      CHECK(buf.Size() == 3);
+    }
+  }
+
+  SECTION("when the required reservation is greater than the old capacity*2, "
+          "it reserves capacity+required size") {
+    auto buf = bytes::Buffer{bytes::View("foo")};
+
+    CHECK(buf.Cap() == 3);
+    CHECK(buf.Size() == 3);
+
+    buf.Reserve(4);
+
+    CHECK(buf.Cap() == 7);
+    CHECK(buf.FreeCap() == 4);
+    CHECK(buf.Size() == 3);
+  }
+}
+
+TEST_CASE("bytes::Buffer::IncSize", "[vereign/bytes]") {
+  SECTION("when IncSize is called on empty buffer, it throws bytes::IncrementOutOfBounds") {
+    auto buf = bytes::Buffer{};
+
+    CHECK_THROWS_AS(
+      buf.IncSize(10),
+      bytes::IncrementOutOfBounds
+    );
+  }
+
+  SECTION("when IncSize is called with value bigger than the free cap, "
+          "it throws bytes::IncrementOutOfBounds") {
+
+    auto buf = bytes::Buffer{3};
+
+    std::strncpy((char*)buf.end(), "bar", 3);
+
+    CHECK_THROWS_AS(
+      buf.IncSize(4),
+      bytes::IncrementOutOfBounds
+    );
+  }
+
+  SECTION("when IncSize is called with zero, it does nothing") {
+    bytes::Buffer buf;
+    buf.IncSize(0);
+
+    CHECK(buf.Size() == 0);
+  }
+
+  SECTION("success") {
+    auto buf = bytes::Buffer{3};
+
+    std::strncpy((char*)buf.end(), "bar", 3);
+
+    buf.IncSize(3);
+
+    CHECK(buf.Size() == 3);
+    CHECK(buf.View().String() == "bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::WriteWithinCap", "[vereign/bytes]") {
+  SECTION("when WriteWithinCap is called on empty buffer, it does nothing") {
+    auto buf = bytes::Buffer{};
+    buf.WriteWithinCap(bytes::View("foo"));
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 0);
+  }
+
+  SECTION("when WriteWithinCap is called with empty source, it does nothing") {
+    auto buf = bytes::Buffer{3};
+    buf.WriteWithinCap(bytes::View());
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 3);
+  }
+
+  SECTION("when WriteWithinCap is called with source bigger than the free cap, "
+          "it copies only free cap amount of bytes") {
+
+    auto buf = bytes::Buffer{3};
+    buf.WriteWithinCap(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 3);
+    CHECK(buf.View().String() == "foo");
+  }
+
+  SECTION("when WriteWithinCap is called with source smaller than the free cap, "
+          "it copies the full source of bytes") {
+
+    auto buf = bytes::Buffer{7};
+    buf.WriteWithinCap(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.View().String() == "foo bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::Write", "[vereign/bytes]") {
+  SECTION("when Write is called on empty buffer, "
+          "it expands the buffer and writes all the source bytes") {
+    auto buf = bytes::Buffer{};
+    buf.Write(bytes::View("foo"));
+
+    CHECK(buf.Size() == 3);
+    CHECK(buf.Cap() == 3);
+    CHECK(buf.View().String() == "foo");
+  }
+
+  SECTION("when Write is called with empty source, it does nothing") {
+    auto buf = bytes::Buffer{3};
+    buf.Write(bytes::View());
+
+    CHECK(buf.Size() == 0);
+    CHECK(buf.Cap() == 3);
+  }
+
+  SECTION("when Write is called with source bigger than the free cap, "
+          "it expands the buffer and writes all the source bytes") {
+
+    auto buf = bytes::Buffer{3};
+    buf.Write(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.View().String() == "foo bar");
+  }
+
+  SECTION("when Write is called with source smaller than the free cap, "
+          "it copies the full source of bytes") {
+
+    auto buf = bytes::Buffer{7};
+    buf.Write(bytes::View("foo bar"));
+
+    CHECK(buf.Size() == 7);
+    CHECK(buf.View().String() == "foo bar");
+  }
+}
+
+TEST_CASE("bytes::Buffer::View", "[vereign/bytes]") {
+  SECTION("when the buffer is empty, it returns empty view") {
+    auto buf = bytes::Buffer{};
+
+    auto v = buf.View();
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the buffer is not empty, it returns a view of the buffer") {
+    auto buf = bytes::Buffer{bytes::View("123")};
+
+    auto v = buf.View();
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "123");
+
+    v = buf.View(1);
+    CHECK(v.Size() == 2);
+    CHECK(v.String() == "23");
+
+    v = buf.View(2);
+    CHECK(v.Size() == 1);
+    CHECK(v.String() == "3");
+
+    v = buf.View(3);
+    CHECK(v.Size() == 0);
+    CHECK(v.String() == "");
+
+    v = buf.View(4);
+    CHECK(v.Size() == 0);
+    CHECK(v.String() == "");
+  }
+}
diff --git a/cpp/tests/vereign/bytes/view_test.cc b/cpp/tests/vereign/bytes/view_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..1f29ab454d9d40225c45c89adddf1dce7f06036d
--- /dev/null
+++ b/cpp/tests/vereign/bytes/view_test.cc
@@ -0,0 +1,236 @@
+#include <vereign/bytes/view.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/errors.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("bytes::View::View", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("view from a pointer and a size") {
+    const uint8_t* data = (uint8_t*)"foo";
+    auto v = bytes::View(data, 3);
+
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "foo");
+  }
+
+  SECTION("view from a void pointer and a size") {
+    const void* data = (void*)"foo";
+    auto v = bytes::View(data, 3);
+
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "foo");
+  }
+
+  SECTION("view from a string") {
+    std::string data = "foo";
+    auto v = bytes::View(data);
+
+    CHECK(v.Size() == 3);
+    CHECK(v.String() == "foo");
+  }
+
+  SECTION("view from a wide string") {
+    std::wstring data = L"foo";
+    auto v = bytes::View(data);
+
+    CHECK(v.Size() == sizeof(wchar_t)*3);
+    CHECK(v.WideString() == L"foo");
+  }
+}
+
+TEST_CASE("bytes::View::Slice", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.Slice(1).Size() == 0);
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.Slice(0).Size() == 3);
+    CHECK(v.Slice(0).String() == "123");
+
+    CHECK(v.Slice(1).Size() == 2);
+    CHECK(v.Slice(1).String() == "23");
+
+    CHECK(v.Slice(1, 2).Size() == 1);
+    CHECK(v.Slice(1, 2).String() == "2");
+
+    CHECK(v.Slice(1, 3).Size() == 2);
+    CHECK(v.Slice(1, 3).String() == "23");
+
+    CHECK(v.Slice(1, 4).Size() == 2);
+    CHECK(v.Slice(1, 4).String() == "23");
+  }
+
+  SECTION("start/end out of bounds") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.Slice(0).Size() == 3);
+    CHECK(v.Slice(0).String() == "123");
+
+    CHECK(v.Slice(3).Size() == 0);
+    CHECK(v.Slice(3).String() == "");
+
+    CHECK(v.Slice(4).Size() == 0);
+    CHECK(v.Slice(4).String() == "");
+
+    CHECK(v.Slice(1, 4).Size() == 2);
+    CHECK(v.Slice(1, 4).String() == "23");
+  }
+}
+
+TEST_CASE("bytes::View::Data", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.Data() == nullptr);
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.Data()[0] == '1');
+    CHECK(v.Data()[1] == '2');
+  }
+}
+
+TEST_CASE("bytes::View::CharData", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.CharData() == nullptr);
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    const char* data = v.CharData();
+
+    CHECK(data[0] == '1');
+    CHECK(data[1] == '2');
+  }
+}
+
+TEST_CASE("bytes::View::WideCharData", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.WideCharData() == nullptr);
+  }
+
+  SECTION("non-empty view") {
+    std::wstring s = L"123";
+    auto v = bytes::View(s);
+
+    const wchar_t* data = v.WideCharData();
+
+    CHECK(data[0] == wchar_t('1'));
+    CHECK(data[1] == wchar_t('2'));
+  }
+}
+
+TEST_CASE("bytes::View::String", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.String() == "");
+  }
+
+  SECTION("non-empty view") {
+    std::string s = "123";
+    auto v = bytes::View(s);
+
+    CHECK(v.String() == "123");
+  }
+}
+
+TEST_CASE("bytes::View::WideString", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK(v.WideString() == L"");
+  }
+
+  SECTION("non-empty view") {
+    std::wstring s = L"123";
+    auto v = bytes::View(s);
+
+    CHECK(v.WideString() == L"123");
+  }
+}
+
+TEST_CASE("bytes::View::operator==", "[vereign/bytes]") {
+  auto s1 = std::string{"foo"};
+  auto s2 = std::string{"foo"};
+
+  CHECK(bytes::View(s1) == bytes::View(s2));
+
+  s2 = "bar";
+  CHECK((bytes::View(s1) == bytes::View(s2)) == false);
+
+  s2 = "";
+  CHECK((bytes::View(s1) == bytes::View(s2)) == false);
+
+  auto b1 = crypto::Rand(16);
+  auto b2 = bytes::Buffer{b1.View()};
+
+  CHECK(bytes::View(b1.begin(), 16) == bytes::View(b2.begin(), 16));
+}
+
+TEST_CASE("bytes::View::operator!=", "[vereign/bytes]") {
+  auto s1 = std::string{"foo"};
+  auto s2 = std::string{"foo"};
+
+  CHECK((bytes::View(s1) != bytes::View(s2)) == false);
+
+  s2 = "bar";
+  CHECK(bytes::View(s1) != bytes::View(s2));
+
+  s2 = "";
+  CHECK(bytes::View(s1) != bytes::View(s2));
+
+  auto b1 = crypto::Rand(16);
+  auto b2 = bytes::Buffer{b1.View()};
+
+  CHECK((bytes::View(b1.begin(), 16) != bytes::View(b2.begin(), 16)) == false);
+}
+
+TEST_CASE("bytes::View::operator[]", "[vereign/bytes]") {
+  SECTION("empty view") {
+    auto v = bytes::View{};
+
+    CHECK_THROWS_AS(
+      v[0],
+      bytes::IndexOutOfBounds
+    );
+  }
+
+  SECTION("non-empty view") {
+    auto s = std::string{"123"};
+    auto v = bytes::View(s);
+
+    CHECK(v[0] == '1');
+    CHECK(v[1] == '2');
+    CHECK(v[2] == '3');
+
+    CHECK_THROWS_AS(
+      v[3],
+      bytes::IndexOutOfBounds
+    );
+  }
+}
diff --git a/cpp/tests/vereign/crypto/aes_test.cc b/cpp/tests/vereign/crypto/aes_test.cc
index 4989c0675fcca437d527000e0924f3662b311e74..cfbb694c4e2eaddfd922670ad528a6984ebb0850 100644
--- a/cpp/tests/vereign/crypto/aes_test.cc
+++ b/cpp/tests/vereign/crypto/aes_test.cc
@@ -10,14 +10,13 @@ using namespace vereign;
 
 TEST_CASE("aes::GCM256Encrypt", "[vereign/crypto/aes][vereign/crypto]") {
   const std::string data{"foo bar"};
-  bytes::Buffer key{32};
-  crypto::Rand(key);
+  auto key = crypto::Rand(32);
 
   bytes::Buffer iv;
   bytes::Buffer tag;
   bytes::Buffer encrypted;
 
-  crypto::aes::GCM256Encrypt(bytes::View(data), key.View(), iv, encrypted, tag);
+  crypto::aes::GCM256Encrypt(bytes::View(data), key.View(), iv, tag, encrypted);
 
   bytes::Buffer decrypted;
   crypto::aes::GCM256Decrypt(encrypted.View(), key.View(), iv.View(), tag.View(), decrypted);
diff --git a/cpp/tests/vereign/crypto/rsa_test.cc b/cpp/tests/vereign/crypto/rsa_test.cc
index b484d6eca07021b0838f95ad6ef56f3ffc815aed..f9f27acb9b3a20cf0621dd886144a6a7f2b7306b 100644
--- a/cpp/tests/vereign/crypto/rsa_test.cc
+++ b/cpp/tests/vereign/crypto/rsa_test.cc
@@ -41,8 +41,7 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa
   SECTION("max size input") {
     auto key = crypto::rsa::GenerateKey(2048);
 
-    bytes::Buffer input{214};
-    crypto::Rand(input);
+    auto input = crypto::Rand(214);
     bytes::Buffer encrypted;
 
     crypto::rsa::PublicKeyEncrypt(key.get(), input.View(), encrypted);
@@ -56,8 +55,7 @@ TEST_CASE("crypto::rsa PublicKeyEncrypt/PrivateKeyDecrypt", "[vereign/crypto/rsa
   SECTION("invalid big input") {
     auto key = crypto::rsa::GenerateKey(2048);
 
-    bytes::Buffer input{215};
-    crypto::Rand(input);
+    auto input = crypto::Rand(215);
     bytes::Buffer encrypted;
 
     CHECK_THROWS_WITH(
diff --git a/cpp/tests/vereign/encoding/base64_test.cc b/cpp/tests/vereign/encoding/base64_test.cc
index 2038a634f9a27e24b85b77aec670b7d432e2be52..0e6316b90d2de75e8f9cefcf9c4772259355982b 100644
--- a/cpp/tests/vereign/encoding/base64_test.cc
+++ b/cpp/tests/vereign/encoding/base64_test.cc
@@ -40,8 +40,7 @@ TEST_CASE("base64::Encode", "[vereign/encoding/base64][vereign/encoding]") {
   }
 
   SECTION("big input must not contain new lines") {
-    bytes::Buffer input{1024};
-    crypto::Rand(input);
+    auto input = crypto::Rand(1024);
     bytes::Buffer encoded;
     encoding::base64::Encode(input.View(), encoded);
 
diff --git a/cpp/tests/vereign/encoding/hex_test.cc b/cpp/tests/vereign/encoding/hex_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..9b4fefea84b7de42c502de478613888a736578d9
--- /dev/null
+++ b/cpp/tests/vereign/encoding/hex_test.cc
@@ -0,0 +1,96 @@
+#include <vereign/encoding/hex.hh>
+
+#include <vereign/crypto/rand.hh>
+#include <vereign/bytes/view_dump.hh>
+
+#include <catch2/catch.hpp>
+
+using namespace vereign;
+
+TEST_CASE("encoding::hex::Encode", "[vereign/encoding/hex][vereign/encoding]") {
+  struct Test {
+    std::string Input;
+    std::string Expected;
+  };
+
+  using namespace std::string_literals;
+
+  std::vector<Test> tests{
+    {"", ""},
+    {"f", "66"},
+    {"fo", "666f"},
+    {"foo", "666f6f"},
+    {"foob", "666f6f62"},
+    {"fooba", "666f6f6261"},
+    {"foobar", "666f6f626172"},
+    {"\x00\x42"s "foobar", "0042666f6f626172"}
+  };
+
+  for (auto& test : tests) {
+    bytes::Buffer encoded;
+    encoding::hex::Encode(bytes::View(test.Input), encoded);
+    CHECK(test.Expected == encoded.View().String());
+  }
+}
+
+TEST_CASE("encoding::hex::Decode", "[vereign/encoding/hex][vereign/encoding]") {
+  struct Test {
+    std::string Expected;
+    std::string Input;
+  };
+
+  using namespace std::string_literals;
+
+  std::vector<Test> tests{
+    {"", ""},
+    {"f", "66"},
+    {"fo", "666f"},
+    {"foo", "666f6f"},
+    {"foob", "666f6f62"},
+    {"fooba", "666f6f6261"},
+    {"foobar", "666f6f626172"},
+    {"\x00\x42"s "foobar", "0042666f6f626172"}
+  };
+
+  for (auto& test : tests) {
+    bytes::Buffer decoded;
+    encoding::hex::Decode(bytes::View(test.Input), decoded);
+    CHECK(test.Expected == decoded.View().String());
+  }
+
+  auto input = crypto::Rand(16);
+
+  bytes::Buffer encoded;
+  encoding::hex::Encode(input.View(), encoded);
+
+  bytes::Buffer decoded;
+  encoding::hex::Decode(encoded.View(), decoded);
+
+  CHECK(input.View() == decoded.View());
+}
+
+TEST_CASE("encoding::hex::EncodeReverse", "[vereign/encoding/hex][vereign/encoding]") {
+  struct Test {
+    std::string Input;
+    std::string Expected;
+  };
+
+  using namespace std::string_literals;
+
+  std::vector<Test> tests{
+    {"", ""},
+    {"f", "66"},
+    {"fo", "6f66"},
+    {"foo", "6f6f66"},
+    {"foob", "626f6f66"},
+    {"fooba", "61626f6f66"},
+    {"foobar", "7261626f6f66"},
+    {"\x00\x42"s "foobar", "7261626f6f664200"}
+  };
+
+  for (auto& test : tests) {
+    bytes::Buffer encoded;
+    encoding::hex::EncodeReverse(bytes::View(test.Input), encoded);
+    CHECK(test.Expected == encoded.View().String());
+  }
+}
diff --git a/cpp/tests/vereign/grpc/identity_api_test.cc b/cpp/tests/vereign/grpc/identity_api_test.cc
new file mode 100644
index 0000000000000000000000000000000000000000..516aa71338dbf10660e61d48b0451f55cb647fc6
--- /dev/null
+++ b/cpp/tests/vereign/grpc/identity_api_test.cc
@@ -0,0 +1,337 @@
+#include <vereign/grpc/server.hh>
+
+#include <vereign/grpc/error_code.hh>
+#include <vereign/core/scope_guard.hh>
+#include <vereign/client_library/passport_api.gen.grpc.pb.h>
+#include <vereign/client_library/types.gen.pb.h>
+#include <vereign/client_library/identity_api.gen.grpc.pb.h>
+#include <vereign/service/identity_service.hh>
+#include <vereign/fs/util.hh>
+#include <vereign/fs/path.hh>
+#include <vereign/test/device.hh>
+#include <vereign/test/service_context.hh>
+
+#ifdef _WIN32
+# include <vereign/ncrypt/rsa.hh>
+#endif
+
+#include <util/env.hh>
+#include <util/protobuf.hh>
+#include <grpcpp/create_channel.h>
+
+#include <catch2/catch.hpp>
+
+TEST_CASE("grpc::IdentityAPI::LoginWithPreviouslyAddedDevice", "[vereign/grpc][.integration]") {
+#ifdef _WIN32
+  SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") {
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+
+    auto provider = vereign::ncrypt::rsa::OpenStorageProvider();
+    auto key = vereign::ncrypt::rsa::LoadKey(
+      provider.Get(),
+      std::string(vereign::kvstore::VereignKeyName)
+    );
+    if (key) {
+      vereign::ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() != "");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::DeviceNotRegistered
+    ));
+  }
+
+  SECTION("when the device key has been changed, it must fail with InvalidIdentity error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    vereign::test::PrepareNewDevice(host, port, public_key, "", storage_path);
+
+    auto provider = vereign::ncrypt::rsa::OpenStorageProvider();
+    auto key = vereign::ncrypt::rsa::LoadKey(
+      provider.Get(),
+      std::string(vereign::kvstore::VereignKeyName)
+    );
+    if (key) {
+      vereign::ncrypt::rsa::DeleteKey(key.Get());
+    }
+
+    vereign::ncrypt::rsa::CreateKey(
+      provider.Get(),
+      2048,
+      std::string(vereign::kvstore::VereignKeyName),
+      {}
+    );
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid identity");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidIdentity
+    ));
+  }
+#else
+  SECTION("when the device is not registered yet, it must fail with DeviceNotRegistered error") {
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+    login_req.set_pin("foo");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() != "");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::DeviceNotRegistered
+    ));
+  }
+
+  SECTION("when the pin is wrong, it must fail with InvalidPinCode error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    const std::string pin = "foo";
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+    login_req.set_pin("invalid_pin");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid pin code");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidPinCode
+    ));
+  }
+
+  SECTION("when the pin is empty, it must fail with InvalidPinCode error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    const std::string pin = "foo";
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+    auto login_resp = vereign::client_library::EmptyResponse{};
+    login_req.set_pin("");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid pin code");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidPinCode
+    ));
+  }
+
+#endif
+
+  SECTION("when the device is not authorized, it must fail with DeviceNotRegistered error") {
+    auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    // the old device is used later for new device confirmation and authorization
+    auto old_storage_path = vereign::fs::TempFilePath("test_db_");
+    auto rm_old_storage_path = vereign::fs::RemoveFileGuard{old_storage_path};
+    auto old_device_ctx = vereign::test::ServiceContext{host, port, old_storage_path};
+    auto old_device = vereign::test::Device{old_device_ctx};
+    old_device.Login(public_key);
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+    const std::string pin = "foo";
+
+    std::string qr_code;
+    std::string action_id;
+
+    {
+
+      vereign::grpc::Server server{"localhost:", host, port, storage_path};
+      auto on_exit = vereign::core::MakeScopeGuard([&server] {
+        server.Shutdown();
+      });
+
+      auto channel = ::grpc::CreateChannel(
+        "localhost:" + std::to_string(server.SelectedPort()),
+        ::grpc::InsecureChannelCredentials()
+      );
+
+      auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+
+      auto register_req = vereign::client_library::LoginFormNewDevice{};
+      auto register_resp = vereign::client_library::LoginFormNewDeviceResponse{};
+      register_req.set_pin(pin);
+
+      ::grpc::ClientContext register_ctx;
+      identity_client->LoginWithNewDevice(&register_ctx, register_req, &register_resp);
+
+      CHECK(register_resp.error() == "");
+      CHECK(register_resp.status() == "OK");
+      REQUIRE(register_resp.code() == "200");
+
+      qr_code = register_resp.data().qrcode();
+      action_id = register_resp.data().actionid();
+    }
+
+    {
+      vereign::grpc::Server server{"localhost:", host, port, storage_path};
+      auto on_exit = vereign::core::MakeScopeGuard([&server] {
+        server.Shutdown();
+      });
+
+      auto channel = ::grpc::CreateChannel(
+        "localhost:" + std::to_string(server.SelectedPort()),
+        ::grpc::InsecureChannelCredentials()
+      );
+
+      auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+      auto login_req = vereign::client_library::LoginFormPreviousAddedDevice{};
+      auto login_resp = vereign::client_library::EmptyResponse{};
+      login_req.set_pin(pin);
+
+      ::grpc::ClientContext login_ctx;
+      identity_client->LoginWithPreviouslyAddedDevice(&login_ctx, login_req, &login_resp);
+
+      CHECK(login_resp.error() == "Error retrieving entity ");
+      CHECK(login_resp.status() == "Error while login");
+      REQUIRE(login_resp.code() == "400");
+
+      old_device.ConfirmNewDevice(qr_code, action_id);
+
+      ::grpc::ClientContext login_ctx_confirmed;
+      identity_client->LoginWithPreviouslyAddedDevice(&login_ctx_confirmed, login_req, &login_resp);
+
+      CHECK(login_resp.error() == "Device unauthorized");
+      CHECK(login_resp.status() == "Error while login");
+      REQUIRE(login_resp.code() == "400");
+    }
+  }
+}
+
+TEST_CASE("grpc::IdentityAPI::LoginWithNewDevice", "[vereign/grpc][.integration]") {
+#ifdef _WIN32
+#else
+  SECTION("when the pin is empty, it must fail with InvalidPinCode error") {
+    auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+    auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+    auto storage_path = vereign::fs::TempDir("test_db_");
+    auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+
+    vereign::grpc::Server server{"localhost:", host, port, storage_path};
+    auto on_exit = vereign::core::MakeScopeGuard([&server] {
+      server.Shutdown();
+    });
+
+    auto channel = ::grpc::CreateChannel(
+      "localhost:" + std::to_string(server.SelectedPort()),
+      ::grpc::InsecureChannelCredentials()
+    );
+
+    auto identity_client = vereign::client_library::IdentityAPI::NewStub(channel);
+    auto login_req = vereign::client_library::LoginFormNewDevice{};
+    auto login_resp = vereign::client_library::LoginFormNewDeviceResponse{};
+    login_req.set_pin("");
+
+    ::grpc::ClientContext login_ctx;
+    identity_client->LoginWithNewDevice(&login_ctx, login_req, &login_resp);
+
+    CHECK(login_resp.error() == "invalid pin code");
+    CHECK(login_resp.status() == vereign::grpc::ClientErrorStatus);
+    REQUIRE(login_resp.code() == vereign::grpc::ErrorCodeAsString(
+      vereign::grpc::ErrorCode::InvalidPinCode
+    ));
+  }
+#endif // _WIN32
+}
diff --git a/cpp/tests/vereign/grpc/server_test.cc b/cpp/tests/vereign/grpc/server_test.cc
index f6b5c38678cc233c2a24b1d9345c9f1afbb67d6e..0f3a18589adfb98ebe6d39bb064c91257439b7f5 100644
--- a/cpp/tests/vereign/grpc/server_test.cc
+++ b/cpp/tests/vereign/grpc/server_test.cc
@@ -16,14 +16,15 @@
 
 #include <catch2/catch.hpp>
 
-using namespace vereign;
-
 TEST_CASE("grpc::Server", "[vereign/grpc/server][.integration]") {
-  // NOTE: use the init_integration_storage tool to create a new test storage
-  auto storage_path = vereign::test::RequireEnv("TEST_VEREIGN_INTEGRATION_STORAGE");
-  auto host = test::RequireEnv("TEST_VEREIGN_API_HOST");
-  auto port = test::GetEnv("TEST_VEREIGN_API_PORT", "https");
-  auto pin = vereign::test::GetEnv("TEST_VEREIGN_PIN", "foo");
+  auto public_key = vereign::test::RequireEnv("TEST_VEREIGN_PUB_KEY");
+  auto host = vereign::test::RequireEnv("TEST_VEREIGN_API_HOST");
+  auto port = vereign::test::GetEnv("TEST_VEREIGN_API_PORT", "https");
+
+  const std::string pin = "foo";
+  auto storage_path = vereign::fs::TempDir("test_db_");
+  auto rm_storage_path = vereign::fs::RemoveAllGuard(storage_path);
+  vereign::test::PrepareNewDevice(host, port, public_key, pin, storage_path);
 
   vereign::grpc::Server server{"localhost:", host, port, storage_path};
   auto on_exit = vereign::core::MakeScopeGuard([&server] {
diff --git a/cpp/tests/vereign/kvstore/crypto_storage_test.cc b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
index 3182030d9fcc211b0eede503ce313c95cc02720c..57e3e2945f9cecb97e84656379190f641256e486 100644
--- a/cpp/tests/vereign/kvstore/crypto_storage_test.cc
+++ b/cpp/tests/vereign/kvstore/crypto_storage_test.cc
@@ -1,6 +1,8 @@
 #include <vereign/kvstore/crypto_storage.hh>
 
+#include <vereign/encoding/errors.hh>
 #include <vereign/kvstore/sqlite_storage.hh>
+#include <vereign/kvstore/errors.hh>
 #include <vereign/crypto/rand.hh>
 #include <vereign/bytes/view_dump.hh>
 #include <vereign/fs/util.hh>
@@ -12,7 +14,7 @@
 
 using namespace vereign;
 
-TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
+TEST_CASE("kvstore::CryptoStorage::Reset", "[vereign/kvstore]") {
   auto storage_path = fs::TempFilePath("test_db_");
   fs::RemoveFileGuard rm{storage_path};
 
@@ -39,12 +41,11 @@ TEST_CASE("CryptoStorage::Reset", "[vereign/kvstore]") {
   }
 }
 
-TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") {
+TEST_CASE("kvstore::CryptoStorage::PutBytes", "[vereign/kvstore]") {
   auto storage_path = fs::TempFilePath("test_db_");
   fs::RemoveFileGuard rm{storage_path};
 
-  bytes::Buffer big_value{100000};
-  crypto::Rand(big_value);
+  auto big_value = crypto::Rand(100000);
   auto kvstorage = kvstore::SqliteStorage(storage_path);
   kvstore::CryptoStorage storage{kvstorage, true};
 
@@ -59,3 +60,40 @@ TEST_CASE("CryptoStorage::PutBytes", "[vereign/kvstore]") {
   auto cmp = std::memcmp(v.View().Data(), big_value.View().Data(), v.Size());
   CHECK(cmp == 0);
 }
+
+TEST_CASE("kvstore::CryptoStorage::GetBytes", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+  kvstore::CryptoStorage storage{kvstorage, true};
+
+  storage.Reset("foo");
+
+  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+    bytes::Buffer v;
+
+    CHECK_THROWS_AS(storage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError);
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the value cannot be decoded, it must throw encoding::Error") {
+    kvstorage.PutInt64("foo", 1);
+
+    bytes::Buffer v;
+
+    CHECK_THROWS_AS(storage.GetBytes("foo", v), encoding::Error);
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the value exists, it must retrieve the value") {
+    storage.PutBytes("foo", bytes::View("bar"));
+
+    bytes::Buffer v;
+    storage.GetBytes("foo", v);
+
+    CHECK(v.View() == bytes::View("bar"));
+  }
+}
diff --git a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
index a815908cbfdb607287bc4ae976466fb4c2cdf8b8..f747ef2e4225b2b4c38b68551ab5cec2fcb538ae 100644
--- a/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
+++ b/cpp/tests/vereign/kvstore/sqlite_storage_test.cc
@@ -17,6 +17,48 @@
 
 using namespace vereign;
 
+TEST_CASE("kvstore::SqliteStorage::GetBytes", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+    bytes::Buffer v;
+
+    CHECK_THROWS_AS(kvstorage.GetBytes("does_not_exists", v), kvstore::ValueNotFoundError);
+
+    CHECK(v.Size() == 0);
+  }
+
+  SECTION("when the value exists, it must retrieve the value") {
+    kvstorage.PutBytes("foo", bytes::View("bar"));
+
+    bytes::Buffer v;
+    kvstorage.GetBytes("foo", v);
+
+    CHECK(v.View() == bytes::View("bar"));
+  }
+}
+
+TEST_CASE("kvstore::SqliteStorage::GetInt64", "[vereign/kvstore]") {
+  auto storage_path = fs::TempFilePath("test_db_");
+  fs::RemoveFileGuard rm{storage_path};
+
+  auto kvstorage = kvstore::SqliteStorage(storage_path);
+
+  SECTION("when the value does not exists, it must throw ValueNotFoundError") {
+    CHECK_THROWS_AS(kvstorage.GetInt64("does_not_exists"), kvstore::ValueNotFoundError);
+  }
+
+  SECTION("when the value exists, it must retrieve the value") {
+    kvstorage.PutInt64("foo", 42);
+
+    auto v = kvstorage.GetInt64("foo");
+
+    CHECK(v == 42);
+  }
+}
 
 TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") {
   auto storage_path = fs::TempFilePath("test_db_");
@@ -32,8 +74,8 @@ TEST_CASE("kvstore::SqliteStorage::DeleteAll", "[vereign/kvstore]") {
 
   kvstorage.DeleteAll();
 
-  CHECK(kvstorage.GetInt64("foo") == 0);
-  CHECK(kvstorage.GetInt64("bar") == 0);
+  CHECK_THROWS_AS(kvstorage.GetInt64("foo"), kvstore::ValueNotFoundError);
+  CHECK_THROWS_AS(kvstorage.GetInt64("bar"), kvstore::ValueNotFoundError);
 }
 
 TEST_CASE("kvstore::SqliteStorage::Lock", "[vereign/kvstore]") {
diff --git a/cpp/tests/vereign/test/device.hh b/cpp/tests/vereign/test/device.hh
index be09ce192dacc6879d81697b5488e24daa40f48c..9ad85bd8a42142f35f7c9a589244c2a8b25fd294 100644
--- a/cpp/tests/vereign/test/device.hh
+++ b/cpp/tests/vereign/test/device.hh
@@ -2,6 +2,7 @@
 #define __TESTS_VEREIGN_TEST_DEVICE_HH
 
 #include <memory>
+#include <string>
 
 namespace vereign::service {
 class IdentityService;