ObsidianDragon - DragonX ImGui Wallet

Full-node GUI wallet for DragonX cryptocurrency.
Built with Dear ImGui, SDL3, and OpenGL3/DX11.

Features:
- Send/receive shielded and transparent transactions
- Autoshield with merged transaction display
- Built-in CPU mining (xmrig)
- Peer management and network monitoring
- Wallet encryption with PIN lock
- QR code generation for receive addresses
- Transaction history with pagination
- Console for direct RPC commands
- Cross-platform (Linux, Windows)
This commit is contained in:
2026-02-26 02:31:52 -06:00
commit 3aee55b49c
306 changed files with 177789 additions and 0 deletions

249
src/util/secure_vault.cpp Normal file
View File

@@ -0,0 +1,249 @@
// ObsidianDragon Wallet - ImGui Edition
// Copyright 2024-2026 The Hush Developers
// Released under the GPLv3
#include "secure_vault.h"
#include "platform.h"
#include <sodium.h>
#include <fstream>
#include <filesystem>
#include <cstring>
#include <algorithm>
#include "../util/logger.h"
namespace fs = std::filesystem;
namespace dragonx {
namespace util {
// Vault file format version
static constexpr uint8_t VAULT_VERSION = 0x01;
// Argon2id parameters — moderate: ~256 MB, ~3 ops
// Tuned for short PINs to make brute-force costly
static constexpr unsigned long long ARGON2_MEMLIMIT = crypto_pwhash_MEMLIMIT_MODERATE;
static constexpr unsigned long long ARGON2_OPSLIMIT = crypto_pwhash_OPSLIMIT_MODERATE;
SecureVault::SecureVault() {
// Ensure libsodium is initialized
if (sodium_init() < 0) {
// sodium_init returns 0 on success, 1 if already initialized, -1 on failure
// We'll proceed anyway — the functions will fail gracefully
}
}
SecureVault::~SecureVault() = default;
std::string SecureVault::getVaultPath() {
return (fs::path(Platform::getConfigDir()) / "vault.dat").string();
}
bool SecureVault::hasVault() const {
std::error_code ec;
return fs::exists(getVaultPath(), ec);
}
bool SecureVault::isValidPin(const std::string& pin) {
if (pin.size() < 4 || pin.size() > 8) return false;
return std::all_of(pin.begin(), pin.end(), ::isdigit);
}
bool SecureVault::deriveKey(const std::string& pin,
const uint8_t* salt, size_t saltLen,
uint8_t* key, size_t keyLen) const {
if (saltLen < crypto_pwhash_SALTBYTES) return false;
int rc = crypto_pwhash(
key, keyLen,
pin.c_str(), pin.size(),
salt,
ARGON2_OPSLIMIT,
ARGON2_MEMLIMIT,
crypto_pwhash_ALG_ARGON2ID13
);
return rc == 0;
}
void SecureVault::secureZero(void* ptr, size_t len) {
sodium_memzero(ptr, len);
}
bool SecureVault::store(const std::string& pin, const std::string& passphrase) {
if (!isValidPin(pin)) {
DEBUG_LOGF("[SecureVault] Invalid PIN format\n");
return false;
}
if (passphrase.empty()) {
DEBUG_LOGF("[SecureVault] Empty passphrase\n");
return false;
}
// Generate random salt and nonce
uint8_t salt[crypto_pwhash_SALTBYTES];
uint8_t nonce[crypto_secretbox_NONCEBYTES];
randombytes_buf(salt, sizeof(salt));
randombytes_buf(nonce, sizeof(nonce));
// Derive key from PIN
uint8_t key[crypto_secretbox_KEYBYTES];
if (!deriveKey(pin, salt, sizeof(salt), key, sizeof(key))) {
DEBUG_LOGF("[SecureVault] Key derivation failed\n");
return false;
}
// Encrypt passphrase
size_t plainLen = passphrase.size();
size_t cipherLen = plainLen + crypto_secretbox_MACBYTES;
std::vector<uint8_t> ciphertext(cipherLen);
int rc = crypto_secretbox_easy(
ciphertext.data(),
reinterpret_cast<const uint8_t*>(passphrase.c_str()),
plainLen,
nonce,
key
);
// Wipe key immediately
secureZero(key, sizeof(key));
if (rc != 0) {
DEBUG_LOGF("[SecureVault] Encryption failed\n");
return false;
}
// Write vault file: version + salt + nonce + ciphertext
std::string vaultPath = getVaultPath();
fs::create_directories(fs::path(vaultPath).parent_path());
std::ofstream out(vaultPath, std::ios::binary);
if (!out.is_open()) {
DEBUG_LOGF("[SecureVault] Cannot create vault file: %s\n", vaultPath.c_str());
return false;
}
out.write(reinterpret_cast<const char*>(&VAULT_VERSION), 1);
out.write(reinterpret_cast<const char*>(salt), sizeof(salt));
out.write(reinterpret_cast<const char*>(nonce), sizeof(nonce));
out.write(reinterpret_cast<const char*>(ciphertext.data()), cipherLen);
out.close();
if (!out.good()) {
DEBUG_LOGF("[SecureVault] Write failed\n");
fs::remove(vaultPath);
return false;
}
DEBUG_LOGF("[SecureVault] Vault saved (%zu bytes ciphertext)\n", cipherLen);
return true;
}
bool SecureVault::retrieve(const std::string& pin, std::string& passphrase) const {
if (!isValidPin(pin)) return false;
std::string vaultPath = getVaultPath();
std::ifstream in(vaultPath, std::ios::binary);
if (!in.is_open()) return false;
// Read entire file
in.seekg(0, std::ios::end);
size_t fileSize = in.tellg();
in.seekg(0);
// Minimum size: 1 (version) + SALTBYTES + NONCEBYTES + MACBYTES + 1 (min plaintext)
size_t minSize = 1 + crypto_pwhash_SALTBYTES + crypto_secretbox_NONCEBYTES
+ crypto_secretbox_MACBYTES + 1;
if (fileSize < minSize) {
DEBUG_LOGF("[SecureVault] Vault file too small\n");
return false;
}
std::vector<uint8_t> data(fileSize);
in.read(reinterpret_cast<char*>(data.data()), fileSize);
in.close();
// Parse header
size_t offset = 0;
uint8_t version = data[offset++];
if (version != VAULT_VERSION) {
DEBUG_LOGF("[SecureVault] Unknown vault version: %d\n", version);
return false;
}
const uint8_t* salt = &data[offset];
offset += crypto_pwhash_SALTBYTES;
const uint8_t* nonce = &data[offset];
offset += crypto_secretbox_NONCEBYTES;
const uint8_t* ciphertext = &data[offset];
size_t cipherLen = fileSize - offset;
size_t plainLen = cipherLen - crypto_secretbox_MACBYTES;
// Derive key
uint8_t key[crypto_secretbox_KEYBYTES];
if (!deriveKey(pin, salt, crypto_pwhash_SALTBYTES, key, sizeof(key))) {
DEBUG_LOGF("[SecureVault] Key derivation failed during retrieve\n");
return false;
}
// Decrypt
std::vector<uint8_t> plaintext(plainLen);
int rc = crypto_secretbox_open_easy(
plaintext.data(),
ciphertext,
cipherLen,
nonce,
key
);
secureZero(key, sizeof(key));
if (rc != 0) {
// Wrong PIN or corrupted data
return false;
}
passphrase.assign(reinterpret_cast<const char*>(plaintext.data()), plainLen);
secureZero(plaintext.data(), plainLen);
return true;
}
bool SecureVault::changePin(const std::string& oldPin, const std::string& newPin) {
std::string passphrase;
if (!retrieve(oldPin, passphrase)) {
return false;
}
bool ok = store(newPin, passphrase);
secureZero(&passphrase[0], passphrase.size());
return ok;
}
bool SecureVault::removeVault() {
std::string vaultPath = getVaultPath();
std::error_code ec;
if (fs::exists(vaultPath, ec)) {
// Overwrite with zeros before removing (best-effort secure delete)
{
std::ifstream probe(vaultPath, std::ios::binary | std::ios::ate);
size_t sz = probe.is_open() ? (size_t)probe.tellg() : 0;
probe.close();
if (sz > 0) {
std::vector<uint8_t> zeros(sz, 0);
std::ofstream zap(vaultPath, std::ios::binary);
if (zap.is_open()) {
zap.write(reinterpret_cast<const char*>(zeros.data()), sz);
}
}
}
return fs::remove(vaultPath, ec);
}
return true; // Didn't exist — that's fine
}
} // namespace util
} // namespace dragonx