#include <sodium.h>
#include <iostream>
#include <string>
#include <vector>
static void die(const char* m){ std::cerr << m << "\n"; std::exit(1); }
static std::vector<unsigned char> hex2bin(const std::string& hex){
std::vector<unsigned char> out(hex.size()/2);
size_t bin_len=0;
if (sodium_hex2bin(out.data(), out.size(), hex.c_str(), hex.size(), nullptr, &bin_len, nullptr) != 0)
die("bad hex");
out.resize(bin_len);
return out;
}
int main(int argc, char** argv){
if (sodium_init() < 0) die("sodium_init failed");
bool decrypt=false;
int argi=1;
if (argc>=2 && std::string(argv[1])=="-d"){ decrypt=true; argi=2; }
if ((decrypt && argc-argi!=2) || (!decrypt && argc-argi!=2)){
std::cerr << "Usage:\n Encrypt: " << argv[0] << " <message> <privkey_hex>\n"
<< " Decrypt: " << argv[0] << " -d <base64> <privkey_hex>\n";
return 1;
}
std::string a = argv[argi];
std::string priv_hex = argv[argi+1];
auto priv = hex2bin(priv_hex);
if (priv.size()!=32) die("privkey must be 32 bytes (64 hex chars)");
// Key derivation: k = BLAKE2b("BTC-LOVE-1" || privkey)
const std::string ctx = "BTC-LOVE-1";
std::vector<unsigned char> k(crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
crypto_generichash_state st;
crypto_generichash_init(&st, nullptr, 0, k.size());
crypto_generichash_update(&st, reinterpret_cast<const unsigned char*>(ctx.data()), ctx.size());
crypto_generichash_update(&st, priv.data(), priv.size());
crypto_generichash_final(&st, k.data(), k.size());
if (!decrypt){
const size_t NONCE_LEN = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES; // 24
std::vector<unsigned char> nonce(NONCE_LEN);
randombytes_buf(nonce.data(), nonce.size());
const unsigned char* ad = reinterpret_cast<const unsigned char*>("Chief Hodler");
const size_t ad_len = 12;
const unsigned char* msg = reinterpret_cast<const unsigned char*>(a.data());
size_t mlen = a.size();
std::vector<unsigned char> ct(mlen + crypto_aead_xchacha20poly1305_ietf_ABYTES);
unsigned long long clen=0;
if (crypto_aead_xchacha20poly1305_ietf_encrypt(
ct.data(), &clen, msg, mlen, ad, ad_len, nullptr,
nonce.data(), k.data()) != 0) die("encrypt failed");
ct.resize(clen);
// package: nonce || ct
std::vector<unsigned char> pack; pack.reserve(nonce.size()+ct.size());
pack.insert(pack.end(), nonce.begin(), nonce.end());
pack.insert(pack.end(), ct.begin(), ct.end());
// base64
size_t b64_len = sodium_base64_ENCODED_LEN(pack.size(), sodium_base64_VARIANT_ORIGINAL);
std::vector<char> b64(b64_len);
sodium_bin2base64(b64.data(), b64.size(),
pack.data(), pack.size(),
sodium_base64_VARIANT_ORIGINAL);
std::cout << b64.data() << "\n";
} else {
// input is base64 pack
const std::string b64 = a;
std::vector<unsigned char> pack(b64.size()); size_t pack_len=0;
if (sodium_base642bin(pack.data(), pack.size(), b64.c_str(), b64.size(),
nullptr, &pack_len, nullptr, sodium_base64_VARIANT_ORIGINAL) != 0)
die("bad base64");
pack.resize(pack_len);
const size_t NONCE_LEN = crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
if (pack.size() < NONCE_LEN + crypto_aead_xchacha20poly1305_ietf_ABYTES) die("cipher too short");
std::vector<unsigned char> nonce(pack.begin(), pack.begin()+NONCE_LEN);
std::vector<unsigned char> ct(pack.begin()+NONCE_LEN, pack.end());
const unsigned char* ad = reinterpret_cast<const unsigned char*>("Chief Hodler");
const size_t ad_len = 12;
std::vector<unsigned char> pt(ct.size()); unsigned long long plen=0;
if (crypto_aead_xchacha20poly1305_ietf_decrypt(
pt.data(), &plen, nullptr, ct.data(), ct.size(),
ad, ad_len, nonce.data(), k.data()) != 0)
die("decryption failed (wrong key or corrupted data)");
pt.resize(plen);
std::cout.write(reinterpret_cast<const char*>(pt.data()), pt.size());
std::cout << "\n";
}
return 0;
}