// DragonX Wallet - HushChat identity derivation (implementation).

#include "chat_identity.h"

#include <sodium.h>

namespace dragonx::chat {

// The KDF context is used as the BLAKE2b key, so its length must sit within the primitive's
// key bounds.
static_assert(kChatIdentityKdfContextLen >= crypto_generichash_KEYBYTES_MIN,
              "chat identity KDF context is shorter than BLAKE2b's minimum key length");
static_assert(kChatIdentityKdfContextLen <= crypto_generichash_KEYBYTES_MAX,
              "chat identity KDF context is longer than BLAKE2b's maximum key length");

const char* chatIdentityStatusName(ChatIdentityStatus status) {
    switch (status) {
        case ChatIdentityStatus::Ready:             return "Ready";
        case ChatIdentityStatus::FeatureDisabled:   return "FeatureDisabled";
        case ChatIdentityStatus::SecretUnavailable: return "SecretUnavailable";
        case ChatIdentityStatus::DerivationFailed:  return "DerivationFailed";
    }
    return "Unknown";
}

std::string chatIdentityPublicKeyHex(const ChatKeyPair& keys) {
    char hex[crypto_kx_PUBLICKEYBYTES * 2 + 1];
    sodium_bin2hex(hex, sizeof hex, keys.public_key.data(), keys.public_key.size());
    return std::string(hex);
}

ChatIdentityResult deriveChatIdentityFromSecret(const std::string& stableSecret,
                                                ChatKeyPair& outKeys,
                                                bool featureEnabled) {
    ChatIdentityResult result;
    auto finish = [&result](ChatIdentityStatus status) -> ChatIdentityResult {
        result.status = status;
        result.error_name = chatIdentityStatusName(status);
        return result;
    };

    if (!featureEnabled) return finish(ChatIdentityStatus::FeatureDisabled);
    if (stableSecret.empty()) return finish(ChatIdentityStatus::SecretUnavailable);
    if (sodium_init() < 0) return finish(ChatIdentityStatus::DerivationFailed);

    unsigned char kxSeed[crypto_kx_SEEDBYTES];
    static_assert(sizeof(kxSeed) == kChatKeyBytes, "kx seed size mismatch");

    const int hashStatus = crypto_generichash(
        kxSeed, sizeof kxSeed,
        reinterpret_cast<const unsigned char*>(stableSecret.data()), stableSecret.size(),
        reinterpret_cast<const unsigned char*>(kChatIdentityKdfContext), kChatIdentityKdfContextLen);
    if (hashStatus != 0) {
        sodium_memzero(kxSeed, sizeof kxSeed);
        return finish(ChatIdentityStatus::DerivationFailed);
    }

    const int keypairStatus =
        crypto_kx_seed_keypair(outKeys.public_key.data(), outKeys.secret_key.data(), kxSeed);
    sodium_memzero(kxSeed, sizeof kxSeed);  // wipe the seed immediately, success or failure
    if (keypairStatus != 0) {
        wipeChatKeyPair(outKeys);
        return finish(ChatIdentityStatus::DerivationFailed);
    }

    result.public_key_hex = chatIdentityPublicKeyHex(outKeys);
    return finish(ChatIdentityStatus::Ready);
}

} // namespace dragonx::chat
