#pragma once // DragonX Wallet - HushChat identity derivation. // // The DragonX-native chat identity is an X25519 (crypto_kx) keypair derived from a stable // per-wallet secret via a domain-separated keyed BLAKE2b KDF. This deliberately does NOT // use SDXL's UTF-8-hex-seed quirk (that quirk is only for the Phase-4 "import an existing // SDXL identity" path). Interop is unaffected: identity derivation is local โ€” peers only // exchange public keys. See docs/_archive/contacts-chat-tab-plan-2026-07-05.md ยง5.6. #include "chat_crypto.h" // ChatKeyPair #include "chat_protocol.h" // hushChatFeatureEnabledAtBuild() #include #include namespace dragonx::chat { // Domain-separation label โ€” used as the BLAKE2b key so a different app/version cannot // derive the same identity from the same wallet secret. A char[] (not const char*) so its // length is a compile-time constant for the KEYBYTES-bounds static_assert. inline constexpr char kChatIdentityKdfContext[] = "DragonX-HushChat-Identity-v1"; inline constexpr std::size_t kChatIdentityKdfContextLen = sizeof(kChatIdentityKdfContext) - 1; enum class ChatIdentityStatus { Ready, FeatureDisabled, // DRAGONX_ENABLE_CHAT off (or caller passed featureEnabled=false) SecretUnavailable, // no stable secret (wallet locked / not open / empty) DerivationFailed // libsodium init or KDF/keypair failure }; const char* chatIdentityStatusName(ChatIdentityStatus status); struct ChatIdentityResult { ChatIdentityStatus status = ChatIdentityStatus::FeatureDisabled; std::string public_key_hex; // 64 lowercase hex chars when Ready const char* error_name = "FeatureDisabled"; // == chatIdentityStatusName(status) }; // Pure, no-I/O derivation: hashes `stableSecret` (variable-length: a mnemonic on lite, a // spending key on full-node) with the KDF context as the BLAKE2b key into a clean 32-byte // crypto_kx seed, then crypto_kx_seed_keypair() into `outKeys`. Deterministic for a given // secret. On any non-Ready result `outKeys` is left wiped. featureEnabled defaults to the // build predicate but tests pass true to exercise the crypto in an OFF build. // // OWNERSHIP: `stableSecret` is BORROWED (const&) and is NOT wiped here โ€” the caller owns it // and MUST sodium_memzero its backing buffer after this returns. The per-variant provider // that fetches the wallet secret (mnemonic / spending key) should hold it in a wipeable // buffer, not a plain std::string literal, in production. ChatIdentityResult deriveChatIdentityFromSecret(const std::string& stableSecret, ChatKeyPair& outKeys, bool featureEnabled = hushChatFeatureEnabledAtBuild()); // 64-char lowercase hex of the public key. std::string chatIdentityPublicKeyHex(const ChatKeyPair& keys); } // namespace dragonx::chat