Skip to content

src/crypto/crypto_store/crypto_store_impl.cpp

Namespaces

Name
sgns
sgns::crypto

Types

Name
using boost::filesystem::directory_iterator DirectoryIterator

Functions

Name
OUTCOME_CPP_DEFINE_CATEGORY_3(sgns::crypto , CryptoStoreError , e )

Types Documentation

using DirectoryIterator

using sgns::crypto::DirectoryIterator = boost::filesystem::directory_iterator;

Functions Documentation

function OUTCOME_CPP_DEFINE_CATEGORY_3

OUTCOME_CPP_DEFINE_CATEGORY_3(
    sgns::crypto ,
    CryptoStoreError ,
    e 
)

Source code

#include "crypto/crypto_store/crypto_store_impl.hpp"

#include <fstream>

#include <gsl/span>
#include "crypto/bip39/mnemonic.hpp"
#include <set>

namespace sgns::crypto {
  using DirectoryIterator = boost::filesystem::directory_iterator;

  namespace {
    template <class T>
    KeyTypeId keyTypeFromBytes(gsl::span<const T> bytes) {
      BOOST_ASSERT_MSG(bytes.size() == 4, "Wrong span size");

      KeyTypeId res = static_cast<uint32_t>(bytes[3])
                      + (static_cast<uint32_t>(bytes[2]) << 8)
                      + (static_cast<uint32_t>(bytes[1]) << 16)
                      + (static_cast<uint32_t>(bytes[0]) << 24);

      return res;
    }

    outcome::result<std::string> loadFileContent(
        const boost::filesystem::path &file_path) {
      if (!boost::filesystem::exists(file_path)) {
        return CryptoStoreError::FILE_DOESNT_EXIST;
      }

      std::ifstream file;
      auto          close_file = gsl::finally(
          [&file]
          {
              if ( file.is_open() )
              {
                  file.close();
              }
          } );

      file.open(file_path.string(), std::ios::in);
      if (!file.is_open()) {
        return CryptoStoreError::FAILED_OPEN_FILE;
      }

      std::string content;
      file >> content;
      return content;
    }
  }  // namespace

  outcome::result<std::pair<KeyTypeId, store::PublicKey>>
  CryptoStoreImpl::parseKeyFileName(std::string_view file_name) const {
    if (file_name.size() < 4 + store::PublicKey::size()) {
      return CryptoStoreError::WRONG_KEYFILE_NAME;
    }

    auto key_type_str = file_name.substr(0, 4);    
    auto key_type = keyTypeFromBytes(
        /*gsl::make_span(key_type_str.begin(), key_type_str.end()));*/
        gsl::make_span( reinterpret_cast<const uint8_t *>(key_type_str.data()), 
                        reinterpret_cast<const uint8_t *>(key_type_str.data())  
                        + key_type_str.size()));   
        //---------- fix ----------------//                                         
    if (!isSupportedKeyType(key_type)) {
      logger_->warn("key type <{}> is not officially supported", key_type_str);
    }
    auto public_key_hex = file_name.substr(4);

    OUTCOME_TRY((auto &&, public_key), store::PublicKey::fromHex(public_key_hex));

    return {key_type, public_key};
  }

  CryptoStoreImpl::Path CryptoStoreImpl::composeKeyPath(
      KeyTypeId key_type, const store::PublicKey &public_key) const {
    auto dir = keys_directory_;
    auto &&key_type_str = decodeKeyTypeId(key_type);
    auto &&public_key_hex = public_key.toHex();

    return dir.append(key_type_str + public_key_hex);
  }

  outcome::result<void> CryptoStoreImpl::storeKeyfile(
      KeyTypeId key_type,
      const store::PublicKey &public_key,
      const store::Seed &seed) {
    std::ofstream file;

    auto dir = keys_directory_;
    auto &&path = composeKeyPath(key_type, public_key);
    file.open(path.string(), std::ios::out | std::ios::trunc);
    if (!file.is_open()) {
      return CryptoStoreError::FAILED_OPEN_FILE;
    }

    file << seed.toHex();

    return outcome::success();
  }

  CryptoStoreImpl::CryptoStoreImpl(
      std::shared_ptr<ED25519Provider> ed25519_provider,
      std::shared_ptr<SR25519Provider> sr25519_provider,
      std::shared_ptr<Secp256k1Provider> secp256k1_provider,
      std::shared_ptr<Bip39Provider> bip39_provider,
      std::shared_ptr<CSPRNG> random_generator)
      : ed25519_provider_(std::move(ed25519_provider)),
        sr25519_provider_(std::move(sr25519_provider)),
        secp256k1_provider_(std::move(secp256k1_provider)),
        bip39_provider_(std::move(bip39_provider)),
        random_generator_(std::move(random_generator)),
        logger_(base::createLogger("CryptoStore")) {
    BOOST_ASSERT(ed25519_provider_ != nullptr);
    BOOST_ASSERT(sr25519_provider_ != nullptr);
    BOOST_ASSERT(secp256k1_provider_ != nullptr);
    BOOST_ASSERT(bip39_provider_ != nullptr);
    BOOST_ASSERT(random_generator_ != nullptr);
  }

  outcome::result<void> CryptoStoreImpl::initialize(Path keys_directory) {
    if (boost::filesystem::exists(keys_directory)) {
      // check whether specified path is a directory
      if (!boost::filesystem::is_directory(keys_directory)) {
        return CryptoStoreError::KEYS_PATH_IS_NOT_DIRECTORY;
      }
    } else {
      // try create directory
      if (!boost::filesystem::create_directory(keys_directory)) {
        return CryptoStoreError::FAILED_CREATE_KEYS_DIRECTORY;
      }
    }
    keys_directory_ = std::move(keys_directory);

    return outcome::success();
  }

  outcome::result<ED25519Keypair> CryptoStoreImpl::generateEd25519Keypair(
      KeyTypeId key_type, std::string_view mnemonic_phrase) {
    OUTCOME_TRY((auto &&, mnemonic), bip39::Mnemonic::parse(mnemonic_phrase));
    OUTCOME_TRY((auto &&, entropy), bip39_provider_->calculateEntropy(mnemonic.words));
    OUTCOME_TRY((auto &&, seed), bip39_provider_->makeSeed(entropy, mnemonic.password));
    if (seed.size() < ED25519Seed::size()) {
      return CryptoStoreError::WRONG_SEED_SIZE;
    }

    OUTCOME_TRY((auto &&, ed_seed),
                ED25519Seed::fromSpan(
                    gsl::make_span(seed).subspan(0, ED25519Seed::size())));
    auto &&pair = ed25519_provider_->generateKeypair(ed_seed);

    ed_keys_[key_type].emplace(pair.public_key, pair.private_key);

    return pair;
  }

  outcome::result<SR25519Keypair> CryptoStoreImpl::generateSr25519Keypair(
      KeyTypeId key_type, std::string_view mnemonic_phrase) {
    OUTCOME_TRY((auto &&, mnemonic), bip39::Mnemonic::parse(mnemonic_phrase));
    OUTCOME_TRY((auto &&, entropy), bip39_provider_->calculateEntropy(mnemonic.words));
    OUTCOME_TRY((auto &&, seed), bip39_provider_->makeSeed(entropy, mnemonic.password));
    if (seed.size() < SR25519Seed::size()) {
      return CryptoStoreError::WRONG_SEED_SIZE;
    }

    OUTCOME_TRY((auto &&, sr_seed),
                SR25519Seed::fromSpan(
                    gsl::make_span(seed).subspan(0, SR25519Seed::size())));
    auto &&pair = sr25519_provider_->generateKeypair(sr_seed);

    sr_keys_[key_type].emplace(pair.public_key, pair.secret_key);

    return pair;
  }

  ED25519Keypair CryptoStoreImpl::generateEd25519Keypair(
      KeyTypeId key_type, const ED25519Seed &seed) {
    auto &&pair = ed25519_provider_->generateKeypair(seed);
    ed_keys_[key_type].emplace(pair.public_key, pair.private_key);
    return pair;
  }

  SR25519Keypair CryptoStoreImpl::generateSr25519Keypair(
      KeyTypeId key_type, const SR25519Seed &seed) {
    auto &&pair = sr25519_provider_->generateKeypair(seed);
    sr_keys_[key_type].emplace(pair.public_key, pair.secret_key);
    return pair;
  }

  outcome::result<ED25519Keypair> CryptoStoreImpl::generateEd25519Keypair(
      KeyTypeId key_type) {
    ED25519Seed seed;
    auto &&bytes = random_generator_->randomBytes(ED25519Seed::size());
    std::copy_n(bytes.begin(), ED25519Seed::size(), seed.begin());

    auto &&pair = ed25519_provider_->generateKeypair(seed);
    BOOST_OUTCOME_TRYV2(auto &&, storeKeyfile(key_type, pair.public_key, seed));

    return pair;
  }

  outcome::result<SR25519Keypair> CryptoStoreImpl::generateSr25519Keypair(
      KeyTypeId key_type) {
    SR25519Seed seed;
    auto &&bytes = random_generator_->randomBytes(SR25519Seed::size());
    std::copy_n(bytes.begin(), SR25519Seed::size(), seed.begin());

    auto &&pair = sr25519_provider_->generateKeypair(seed);
    BOOST_OUTCOME_TRYV2(auto &&, storeKeyfile(key_type, pair.public_key, seed));

    return pair;
  }

  outcome::result<ED25519Keypair> CryptoStoreImpl::findEd25519Keypair(
      KeyTypeId key_type, const ED25519PublicKey &pk) const {
    // try find in memory
    if (ed_keys_.count(key_type) > 0) {
      const auto &map = ed_keys_.at(key_type);
      if (auto it = map.find(pk); it != map.end()) {
        return ED25519Keypair{it->second, it->first};
      }
    }
    // try find in filesystem
    auto path = composeKeyPath(key_type, pk);
    if (!boost::filesystem::exists(path)) {
      return CryptoStoreError::KEY_NOT_FOUND;
    }
    OUTCOME_TRY((auto &&, content), loadFileContent(path));
    OUTCOME_TRY((auto &&, seed), ED25519Seed::fromHex(content));

    return ed25519_provider_->generateKeypair(seed);
  }

  outcome::result<SR25519Keypair> CryptoStoreImpl::findSr25519Keypair(
      KeyTypeId key_type, const SR25519PublicKey &pk) const {
    // try find in memory
    if (sr_keys_.count(key_type) > 0) {
      const auto &map = sr_keys_.at(key_type);
      if (auto it = map.find(pk); it != map.end()) {
        return SR25519Keypair{it->second, it->first};
      }
    }
    // try find in filesystem
    auto path = composeKeyPath(key_type, pk);
    if (!boost::filesystem::exists(path)) {
      return CryptoStoreError::KEY_NOT_FOUND;
    }
    OUTCOME_TRY((auto &&, content), loadFileContent(path));
    OUTCOME_TRY((auto &&, seed), ED25519Seed::fromHex(content));

    return sr25519_provider_->generateKeypair(seed);
  }

  CryptoStore::ED25519Keys CryptoStoreImpl::getEd25519PublicKeys(
      KeyTypeId key_type) const {
    std::set<ED25519PublicKey> keys;
    // iterate over in-memory map
    if (ed_keys_.find(key_type) != ed_keys_.end()) {
      const auto &map = ed_keys_.at(key_type);
      for (const auto &k : map) {
        keys.emplace(k.first);
      }
    }

    // iterate over fs
    for (DirectoryIterator it(keys_directory_), end{}; it != end; ++it) {
      if (!boost::filesystem::is_regular_file(*it)) {
        continue;
      }
      auto info = parseKeyFileName(it->path().filename().string());
      if (!info) {
        continue;
      }
      auto &[id, pk] = info.value();
      if (id == key_type && keys.count(pk) == 0) {
        auto &&content = loadFileContent(it->path());
        if (!content) {
          logger_->error("failed to load keyfile {} : {}",
                         it->path().string(),
                         content.error().message());
          continue;
        }

        auto &&seed = ED25519Seed::fromHex(content.value());
        if (!seed) {
          logger_->error("failed to load seed from keyfile {} : {}",
                         it->path().string(),
                         seed.error().message());
          continue;
        }
        auto &&pair = ed25519_provider_->generateKeypair(seed.value());
        if (pair.public_key == pk) {
          keys.emplace(pk);
        }
      }
    }

    return { keys.begin(), keys.end() };
  }

  CryptoStore::SR25519Keys CryptoStoreImpl::getSr25519PublicKeys(
      KeyTypeId key_type) const {
    std::set<SR25519PublicKey> keys;
    // iterate over in-memory map
    if (sr_keys_.find(key_type) != sr_keys_.end()) {
      const auto &map = ed_keys_.at(key_type);
      for (const auto &k : map) {
        keys.emplace(k.first);
      }
    }

    // iterate over fs
    for (DirectoryIterator it(keys_directory_), end{}; it != end; ++it) {
      if (!boost::filesystem::is_regular_file(*it)) {
        continue;
      }
      auto info = parseKeyFileName(it->path().filename().string());
      if (!info) {
        continue;
      }
      auto &[id, pk] = info.value();
      if (id == key_type && keys.count(pk) == 0) {
        auto &&content = loadFileContent(it->path());
        if (!content) {
          logger_->error("failed to load keyfile {} : {}",
                         it->path().string(),
                         content.error().message());
          continue;
        }

        auto &&seed = SR25519Seed::fromHex(content.value());
        if (!seed) {
          logger_->error("failed to load seed from keyfile {} : {}",
                         it->path().string(),
                         seed.error().message());
          continue;
        }
        auto &&pair = sr25519_provider_->generateKeypair(seed.value());
        if (pair.public_key == pk) {
          keys.emplace(pk);
        }
      }
    }

    return { keys.begin(), keys.end() };
  }
}  // namespace sgns::crypto

OUTCOME_CPP_DEFINE_CATEGORY_3(sgns::crypto, CryptoStoreError, e) {
  using E = sgns::crypto::CryptoStoreError;
  switch (e) {
    case E::WRONG_KEYFILE_NAME:
      return "specified file name is not a valid key file";
    case E::UNSUPPORTED_KEY_TYPE:
      return "key type is not supported";
    case E::UNSUPPORTED_CRYPTO_TYPE:
      return "cryptographic type is not supported";
    case E::NOT_REGULAR_FILE:
      return "provided file is not regular";
    case E::FAILED_OPEN_FILE:
      return "failed to open file for reading";
    case E::FILE_DOESNT_EXIST:
      return "file doesn't exist";
    case E::INVALID_FILE_FORMAT:
      return "specified key file is invalid";
    case E::INCONSISTENT_KEYFILE:
      return "key file is inconsistent, public key != derived public key";
    case E::WRONG_SEED_SIZE:
      return "wrong seed size";
    case E::KEY_NOT_FOUND:
      return "key not found";
    case E::KEYS_PATH_IS_NOT_DIRECTORY:
      return "specified path is not a directory";
    case E::FAILED_CREATE_KEYS_DIRECTORY:
      return "failed to create directory";
  }
  return "Unknown CryptoStoreError code";
}

Updated on 2026-03-04 at 13:10:44 -0800