LNS: Lighter and newer encryption

The replaces the LNS encryption starting in HF16 with libsodium's
XChaCha20-Poly1305 (which is recommended over the default encryption
nowadays), and replaces the encryption key with a keyed blake2b hash
rather than argon2.

This also adds a nonce because, after reading the libsodium
documentation, I'm concerned that using a fixed nonce means we are
potentially leaking the name on updates.  This complicates things a bit
-- if doing external signing you need to generate an encrypted value and
sign that, rather than being able to regenerate the encrypted value.

So we have:

name_hash -- blake2b(name), same as before
enc_key -- blake2b(name, key=name_hash), instead of argon2
nonce -- randomly generated, and tacked on the end of the encryption
         value (previously nonce was all-0s).

This requires changing the encryption so that you generate an encrypted
value for the update, rather than just using the update parameters, then
you provided this encrypted value when submitting an update with
external signature.

Code-related changes (aside from implementation the above): this moves
the encryption/decryption functions into the mapping_value itself, and
adds an encrypted state (so that you can encrypt/decrypt a mapping_value
in-place without needing to copy data around).

(Also note that this commit is almost certainly not "clean" -- I think
some of it leaked into the following commit, and some of that commit may
have leaked here, but this separated most of it).
This commit is contained in:
Jason Rhinelander 2020-09-14 22:43:59 -03:00
parent f322d5ffa7
commit 6d81e03bf9
15 changed files with 466 additions and 124 deletions

View File

@ -24,7 +24,9 @@ extern "C"
#include <sodium/crypto_generichash_blake2b.h>
#include <sodium/crypto_pwhash.h>
#include <sodium/crypto_secretbox.h>
#include <sodium/crypto_aead_xchacha20poly1305.h>
#include <sodium/crypto_sign.h>
#include <sodium/randombytes.h>
}
#undef LOKI_DEFAULT_LOG_CATEGORY
@ -81,25 +83,25 @@ enum struct mapping_record_column
owner_id,
backup_owner_id,
update_height,
expiration_height,
_count,
};
static char const *mapping_record_column_string(mapping_record_column col)
static constexpr unsigned char OLD_ENCRYPTION_NONCE[crypto_secretbox_NONCEBYTES] = {};
std::pair<std::basic_string_view<unsigned char>, std::basic_string_view<unsigned char>> lns::mapping_value::value_nonce(mapping_type type) const
{
switch (col)
std::pair<std::basic_string_view<unsigned char>, std::basic_string_view<unsigned char>> result;
auto& [head, tail] = result;
head = {buffer.data(), len};
if ((type == mapping_type::session && len != SESSION_PUBLIC_KEY_BINARY_LENGTH + crypto_aead_xchacha20poly1305_ietf_ABYTES + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES)
|| len < crypto_aead_xchacha20poly1305_ietf_NPUBBYTES /* shouldn't occur, but just in case */)
tail = {OLD_ENCRYPTION_NONCE, sizeof(OLD_ENCRYPTION_NONCE)};
else
{
case mapping_record_column::id: return "id";
case mapping_record_column::type: return "type";
case mapping_record_column::name_hash: return "name_hash";
case mapping_record_column::encrypted_value: return "encrypted_value";
case mapping_record_column::txid: return "txid";
case mapping_record_column::prev_txid: return "prev_txid";
case mapping_record_column::register_height: return "register_height";
case mapping_record_column::update_height: return "update_height";
case mapping_record_column::owner_id: return "owner_id";
case mapping_record_column::backup_owner_id: return "backup_owner_id";
default: return "xx_invalid";
tail = head.substr(len - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
head.remove_suffix(crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
}
return result;
}
std::string lns::mapping_value::to_readable_value(cryptonote::network_type nettype, lns::mapping_type type) const
@ -387,6 +389,7 @@ mapping_record sql_get_mapping_from_statement(sql_compiled_statement& statement)
return result;
}
result.encrypted_value.len = value.size();
result.encrypted_value.encrypted = true;
memcpy(&result.encrypted_value.buffer[0], value.data(), value.size());
}
@ -986,13 +989,23 @@ bool validate_mapping_value(cryptonote::network_type nettype, mapping_type type,
return true;
}
bool validate_encrypted_mapping_value(mapping_type type, std::string const &value, std::string *reason)
static_assert(SODIUM_ENCRYPTION_EXTRA_BYTES == crypto_aead_xchacha20poly1305_ietf_ABYTES + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
static_assert(SODIUM_ENCRYPTION_EXTRA_BYTES >= crypto_secretbox_MACBYTES);
bool mapping_value::validate_encrypted(mapping_type type, std::string_view value, mapping_value* blob, std::string *reason)
{
if (blob) *blob = {};
std::stringstream err_stream;
int max_value_len = crypto_secretbox_MACBYTES;
if (is_lokinet_type(type)) max_value_len += LOKINET_ADDRESS_BINARY_LENGTH;
else if (type == mapping_type::session) max_value_len += SESSION_PUBLIC_KEY_BINARY_LENGTH;
else if (type == mapping_type::wallet) max_value_len += WALLET_ACCOUNT_BINARY_LENGTH;
int value_len = crypto_aead_xchacha20poly1305_ietf_ABYTES + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
if (is_lokinet_type(type)) value_len += LOKINET_ADDRESS_BINARY_LENGTH;
else if (type == mapping_type::wallet) value_len += WALLET_ACCOUNT_BINARY_LENGTH;
else if (type == mapping_type::session)
{
value_len += SESSION_PUBLIC_KEY_BINARY_LENGTH;
// Allow an HF15 argon2 encrypted value which doesn't contain a nonce:
if (value.size() == value_len - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES)
value_len -= crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
}
else
{
if (reason)
@ -1252,59 +1265,178 @@ std::string name_to_base64_hash(std::string const &name)
return result;
}
struct alignas(size_t) secretbox_secret_key_ { unsigned char data[crypto_secretbox_KEYBYTES]; };
using secretbox_secret_key = epee::mlocked<tools::scrubbed<secretbox_secret_key_>>;
struct alignas(size_t) secretbox_secret_key {
unsigned char data[crypto_aead_xchacha20poly1305_ietf_KEYBYTES];
static bool name_to_encryption_key(std::string const &name, secretbox_secret_key &out)
secretbox_secret_key& operator=(const crypto::hash& h) {
static_assert(sizeof(secretbox_secret_key::data) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES);
std::memcpy(data, h.data, sizeof(data));
return *this;
}
};
// New (8.x):
// We encrypt using xchacha20-poly1305; for the encryption key we use the (secret) keyed hash:
// H(name, key=H(name)). Note that H(name) is public info but this keyed hash is known only to the
// resolver.
//
// Note that the name must *already* be lower-cased (we do not transform or validate that here).
//
// If the name hash is already available then it can be passed by pointer as the second argument,
// otherwise pass nullptr to calculate the hash when needed. (Note that name_hash is not used when
// heavy=true).
static void name_to_encryption_key(std::string_view name, const crypto::hash* name_hash, secretbox_secret_key &out)
{
static_assert(sizeof(out) >= crypto_secretbox_KEYBYTES, "Encrypting key needs to have sufficient space for running encryption functions via libsodium");
static unsigned char constexpr SALT[crypto_pwhash_SALTBYTES] = {};
bool result = (crypto_pwhash(out.data, sizeof(out), name.data(), name.size(), SALT, crypto_pwhash_OPSLIMIT_MODERATE, crypto_pwhash_MEMLIMIT_MODERATE, crypto_pwhash_ALG_ARGON2ID13) == 0);
return result;
static_assert(sizeof(out) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES, "Encrypting key needs to have sufficient space for running encryption functions via libsodium");
crypto::hash name_hash_;
if (!name_hash)
name_hash = &(name_hash_ = name_to_hash(name));
out = name_to_hash(name, *name_hash);
}
static unsigned char const ENCRYPTION_NONCE[crypto_secretbox_NONCEBYTES] = {}; // NOTE: Not meant to be extremely secure, just use an empty nonce
bool encrypt_mapping_value(std::string const &name, mapping_value const &value, mapping_value &encrypted_value)
// Old (7.x) "heavy" encryption:
//
// We encrypt using the older xsalsa20-poly1305 encryption scheme, and for encryption key we use an
// expensive argon2 "moderate" hash of the name (with null salt).
static constexpr unsigned char OLD_ENC_SALT[crypto_pwhash_SALTBYTES] = {};
static bool name_to_encryption_key_argon2(std::string_view name, secretbox_secret_key &out)
{
static_assert(mapping_value::BUFFER_SIZE >= SESSION_PUBLIC_KEY_BINARY_LENGTH + crypto_secretbox_MACBYTES, "Value blob assumes the largest size required, all other values should be able to fit into this buffer");
static_assert(mapping_value::BUFFER_SIZE >= LOKINET_ADDRESS_BINARY_LENGTH + crypto_secretbox_MACBYTES, "Value blob assumes the largest size required, all other values should be able to fit into this buffer");
static_assert(mapping_value::BUFFER_SIZE >= WALLET_ACCOUNT_BINARY_LENGTH + crypto_secretbox_MACBYTES, "Value blob assumes the largest size required, all other values should be able to fit into this buffer");
static_assert(sizeof(out) == crypto_secretbox_KEYBYTES, "Encrypting key needs to have sufficient space for running encryption functions via libsodium");
return 0 == crypto_pwhash(
out.data, sizeof(out.data),
name.data(), name.size(),
OLD_ENC_SALT,
crypto_pwhash_OPSLIMIT_MODERATE,
crypto_pwhash_MEMLIMIT_MODERATE,
crypto_pwhash_ALG_ARGON2ID13);
}
bool result = false;
size_t const encryption_len = value.len + crypto_secretbox_MACBYTES;
if (encryption_len > encrypted_value.buffer.size())
bool mapping_value::encrypt(std::string_view name, const crypto::hash* name_hash, bool deprecated_heavy)
{
assert(!encrypted);
if (encrypted) return false;
size_t const encryption_len = len + (deprecated_heavy
? crypto_secretbox_MACBYTES
: crypto_aead_xchacha20poly1305_ietf_ABYTES + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
if (encryption_len > buffer.size())
{
MERROR("Encrypted value pre-allocated buffer too small=" << encrypted_value.buffer.size() << ", required=" << encryption_len);
return result;
MERROR("Encrypted value pre-allocated buffer too small=" << buffer.size() << ", required=" << encryption_len);
return false;
}
encrypted_value = {};
encrypted_value.len = encryption_len;
decltype(buffer) enc_buffer;
secretbox_secret_key skey;
if (name_to_encryption_key(name, skey))
result = (crypto_secretbox_easy(encrypted_value.buffer.data(), value.buffer.data(), value.len, ENCRYPTION_NONCE, reinterpret_cast<unsigned char *>(&skey)) == 0);
return result;
}
bool decrypt_mapping_value(std::string const &name, mapping_value const &encrypted_value, mapping_value &value)
{
bool result = false;
if (encrypted_value.len <= crypto_secretbox_MACBYTES)
if (deprecated_heavy)
{
MERROR("Encrypted value is too short=" << encrypted_value.len << ", at least required=" << crypto_secretbox_MACBYTES + 1);
return result;
if (name_to_encryption_key_argon2(name, skey))
encrypted = (crypto_secretbox_easy(
enc_buffer.data(),
buffer.data(), len,
OLD_ENCRYPTION_NONCE,
skey.data) == 0);
}
else
{
name_to_encryption_key(name, name_hash, skey);
unsigned long long actual_length;
// Create a random nonce:
auto* nonce = &enc_buffer[encryption_len - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES];
randombytes_buf(nonce, crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
encrypted = 0 == crypto_aead_xchacha20poly1305_ietf_encrypt(
&enc_buffer[0], &actual_length,
&buffer[0], len,
nullptr, 0, // additional data
nullptr, // nsec, always nullptr according to libsodium docs (just here for API compat)
nonce,
skey.data);
if (encrypted) assert(actual_length == encryption_len - crypto_aead_xchacha20poly1305_ietf_NPUBBYTES);
}
value = {};
value.len = encrypted_value.len - crypto_secretbox_MACBYTES;
if (encrypted)
{
len = encryption_len;
buffer = enc_buffer;
}
return encrypted;
}
bool mapping_value::decrypt(std::string_view name, mapping_type type, const crypto::hash* name_hash)
{
assert(encrypted);
if (!encrypted) return false;
size_t dec_length;
decltype(buffer) dec_buffer;
secretbox_secret_key skey;
if (name_to_encryption_key(name, skey))
result = crypto_secretbox_open_easy(value.buffer.data(), encrypted_value.buffer.data(), encrypted_value.len, ENCRYPTION_NONCE, reinterpret_cast<unsigned char *>(&skey)) == 0;
// Check for an old-style, argon2-based encryption, used before HF16. (After HF16 we use a much
// faster blake2b-hashed key, and a random nonce appended to the end.)
if (type == mapping_type::session && len == SESSION_PUBLIC_KEY_BINARY_LENGTH + crypto_secretbox_MACBYTES)
{
dec_length = SESSION_PUBLIC_KEY_BINARY_LENGTH;
encrypted = !(name_to_encryption_key_argon2(name, skey) &&
0 == crypto_secretbox_open_easy(dec_buffer.data(), buffer.data(), len, OLD_ENCRYPTION_NONCE, skey.data));
}
else
{
switch(type) {
case mapping_type::session: dec_length = SESSION_PUBLIC_KEY_BINARY_LENGTH; break;
case mapping_type::lokinet: dec_length = LOKINET_ADDRESS_BINARY_LENGTH; break;
case mapping_type::wallet: dec_length = WALLET_ACCOUNT_BINARY_LENGTH; break;
default: MERROR("Invalid mapping_type passed to mapping_value::decrypt"); return false;
}
auto expected_len = dec_length + crypto_aead_xchacha20poly1305_ietf_ABYTES + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES;
if (len != expected_len)
{
MERROR("Encrypted value size is invalid=" << len << ", expected=" << expected_len);
return false;
}
const auto& [enc, nonce] = value_nonce(type);
name_to_encryption_key(name, name_hash, skey);
unsigned long long actual_length;
encrypted = !(0 == crypto_aead_xchacha20poly1305_ietf_decrypt(
dec_buffer.data(), &actual_length,
nullptr, // nsec (always null for this algo)
enc.data(), enc.size(),
nullptr, 0, // additional data
nonce.data(),
skey.data));
if (!encrypted) assert(actual_length == dec_length);
}
if (!encrypted) // i.e. decryption success
{
len = dec_length;
buffer = dec_buffer;
}
return !encrypted;
}
mapping_value mapping_value::make_encrypted(std::string_view name, const crypto::hash* name_hash, bool deprecated_heavy) const
{
mapping_value result{*this};
result.encrypt(name, name_hash, deprecated_heavy);
assert(result.encrypted);
return result;
}
mapping_value mapping_value::make_decrypted(std::string_view name, const crypto::hash* name_hash) const
{
mapping_value result{*this};
result.encrypt(name, name_hash);
assert(!result.encrypted);
return result;
}
static bool build_default_tables(sqlite3 *db)
{

View File

@ -32,17 +32,65 @@ constexpr size_t LOKINET_ADDRESS_BINARY_LENGTH = sizeof(crypto::ed25519_publi
constexpr size_t SESSION_DISPLAY_NAME_MAX = 64;
constexpr size_t SESSION_PUBLIC_KEY_BINARY_LENGTH = 1 + sizeof(crypto::ed25519_public_key); // Session keys at prefixed with 0x05 + ed25519 key
constexpr size_t SODIUM_ENCRYPTION_EXTRA_BYTES = 40; // crypto_aead_xchacha20poly1305_ietf_ABYTES (16) + crypto_aead_xchacha20poly1305_ietf_NPUBBYTES (24), but we don't include sodium here
struct mapping_value
{
static size_t constexpr BUFFER_SIZE = 255;
static size_t constexpr BUFFER_SIZE = std::max({WALLET_ACCOUNT_BINARY_LENGTH, LOKINET_ADDRESS_BINARY_LENGTH, SESSION_PUBLIC_KEY_BINARY_LENGTH}) + SODIUM_ENCRYPTION_EXTRA_BYTES;
std::array<uint8_t, BUFFER_SIZE> buffer;
bool encrypted;
size_t len;
std::string to_string() const { return std::string{to_view()}; }
std::string_view to_view() const { return {reinterpret_cast<const char*>(buffer.data()), len}; }
std::string to_readable_value(cryptonote::network_type nettype, mapping_type type) const;
bool operator==(mapping_value const &other) const { return other.to_view() == to_view(); }
// View the buffer as a encrypted value & nonce pair (the nonce is the last 24 bytes). For older
// session values the nonce will be all 0 bytes *if* the encrypted value is not the proper length
// for an including-the-nonce value. For newer session and all others the nonce is always
// present.
std::pair<std::basic_string_view<unsigned char>, std::basic_string_view<unsigned char>> value_nonce(mapping_type type) const;
bool operator==(mapping_value const &other) const { return encrypted == other.encrypted && other.to_view() == to_view(); }
bool operator==(std::string_view other) const { return other == to_view(); }
// Encrypts the mapping value in-place given the name, suitable for storing into the LNS DB. Only
// basic overflow validation is attempted, values should be pre-validated in the validate*
// functions.
//
// name_hash - pointer to a pre-computed name hash, if available. If nullptr then the hash is
// computed as needed.
// deprecated_heavy - if true use the deprecated argon2 hashing for the encryption key; this
// argument is required for hf15, but shouldn't be used afterwards (except for testing purposes).
//
// Return true if encryption was successful, after which *this will now contain the encrypted value.
//
// If the value is *already* encrypted this fails via assert (in debug compilation) or returns
// false.
bool encrypt(std::string_view name, const crypto::hash* name_hash = nullptr, bool deprecated_heavy = false);
// Makes a copy of *this, calls encrypt() on it, and returns it. See encrypt() above.
mapping_value make_encrypted(std::string_view name, const crypto::hash* name_hash = nullptr, bool deprecated_heavy = false) const;
// Decrypts the mapping value given the name and mapping type. If the name hash is pre-computed
// it can be passed in.
//
// Returns true if decryption was successful, after which *this will now contain the decrypted value.
//
// If the value is *already* decrypted this fails via assert (in debug compilation) or returns
// false.
bool decrypt(std::string_view name, mapping_type type, const crypto::hash* name_hash = nullptr);
// Makes a copy of *this, calls decrypt() on it, and returns it. See decrypt() above.
mapping_value make_decrypted(std::string_view name, const crypto::hash* name_hash = nullptr) const;
// Validate a human readable mapping value representation in 'value' and write the binary form into 'blob'.
// value: if type is session, 66 character hex string of an ed25519 public key (with 05 prefix)
// lokinet, 52 character base32z string of an ed25519 public key
// wallet, the wallet public address string
// blob: (optional) if function returns true, validate will load the binary data into blob (ready for encryption via encrypt())
static bool validate(cryptonote::network_type nettype, mapping_type type, std::string_view value, mapping_value *blob = nullptr, std::string *reason = nullptr);
// blob: (optional) if function returns true then the value will be loaded into the given
// mapping_value, ready for decryption via decrypt().
static bool validate_encrypted(mapping_type type, std::string_view value, mapping_value *blob = nullptr, std::string *reason = nullptr);
};
inline std::ostream &operator<<(std::ostream &os, mapping_value const &v) { return os << lokimq::to_hex(v.to_view()); }

View File

@ -3176,6 +3176,11 @@ Pending or Failed: "failed"|"pending", "out", Lock, Checkpointed, Time, Amount*
tr(USAGE_LNS_UPDATE_MAPPING),
tr(tools::wallet_rpc::LNS_UPDATE_MAPPING::description));
m_cmd_binder.set_handler("lns_encrypt",
[this](const auto& x) { return lns_encrypt(x); },
tr(USAGE_LNS_ENCRYPT),
tr("Encrypts a LNS mapping value with a given name; primarily intended for use with external mapping update signing"));
m_cmd_binder.set_handler("lns_print_owners_to_names",
[this](const auto& x) { return lns_print_owners_to_names(x); },
tr(USAGE_LNS_PRINT_OWNERS_TO_NAMES),
@ -6608,6 +6613,61 @@ bool simple_wallet::lns_update_mapping(const std::vector<std::string>& args)
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::lns_encrypt(std::vector<std::string> args)
{
std::string typestr = eat_named_argument(args, LNS_TYPE_PREFIX);
if (args.size() != 2)
{
PRINT_USAGE(USAGE_LNS_ENCRYPT);
return false;
}
const auto& name = args[0];
const auto& value = args[1];
lns::mapping_type type;
if (auto t = guess_lns_type(*m_wallet, typestr, name, value))
type = *t;
else return false;
if (value.size() > lns::mapping_value::BUFFER_SIZE)
{
fail_msg_writer() << "LNS value '" << value << "' is too long";
return false;
}
std::string reason;
std::optional<uint8_t> hf_version = m_wallet->get_hard_fork_version();
if (!hf_version)
{
tools::fail_msg_writer() << tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED;
return false;
}
if (!lns::validate_lns_name(type, name, &reason))
{
tools::fail_msg_writer() << "Invalid LNS name '" << name << "': " << reason;
return false;
}
lns::mapping_value mval;
if (!lns::mapping_value::validate(m_wallet->nettype(), type, value, &mval, &reason))
{
tools::fail_msg_writer() << "Invalid LNS value '" << value << "': " << reason;
return false;
}
bool old_argon2 = type == lns::mapping_type::session && *hf_version < cryptonote::network_version_16;
if (!mval.encrypt(name, nullptr, old_argon2))
{
tools::fail_msg_writer() << "Value encryption failed";
return false;
}
tools::success_msg_writer() << "encrypted value=" << lokimq::to_hex(mval.to_view());
return true;
}
//----------------------------------------------------------------------------------------------------
bool simple_wallet::lns_make_update_mapping_signature(const std::vector<std::string> &args)
{
if (!try_connect_to_daemon())

View File

@ -184,6 +184,7 @@ namespace cryptonote
bool lns_buy_mapping(const std::vector<std::string> &args);
bool lns_update_mapping(const std::vector<std::string> &args);
bool lns_make_update_mapping_signature(const std::vector<std::string> &args);
bool lns_encrypt(std::vector<std::string> args);
bool lns_print_owners_to_names(const std::vector<std::string> &args);
bool lns_print_name_to_owners(const std::vector<std::string> &args);

View File

@ -8700,7 +8700,7 @@ static lns_prepared_args prepare_tx_extra_loki_name_system_values(wallet2 const
if (!lns::validate_mapping_value(wallet.nettype(), type, *value, &binary_value, reason))
return result;
if (!lns::encrypt_mapping_value(name, binary_value, result.encrypted_value))
if (!result.encrypted_value.encrypt(name, &result.name_hash))
{
if (reason) *reason = "Fail to encrypt mapping value=" + *value;
return {};

View File

@ -3180,18 +3180,47 @@ namespace {
// Decrypt value
//
// ---------------------------------------------------------------------------------------------
lns::mapping_value encrypted_value = {};
encrypted_value.len = req.encrypted_value.size() / 2;
lokimq::from_hex(req.encrypted_value.begin(), req.encrypted_value.end(), encrypted_value.buffer.begin());
lns::mapping_value value = {};
if (!lns::decrypt_mapping_value(req.name, encrypted_value, value))
value.len = req.encrypted_value.size() / 2;
value.encrypted = true;
lokimq::from_hex(req.encrypted_value.begin(), req.encrypted_value.end(), value.buffer.begin());
if (!value.decrypt(req.name, type))
throw wallet_rpc_error{error_code::LNS_VALUE_NOT_HEX, "Value decryption failure"};
res.value = value.to_readable_value(m_wallet->nettype(), type);
return res;
}
LNS_ENCRYPT_VALUE::response wallet_rpc_server::invoke(LNS_ENCRYPT_VALUE::request&& req)
{
require_open();
if (req.value.size() > lns::mapping_value::BUFFER_SIZE)
throw wallet_rpc_error{error_code::LNS_VALUE_TOO_LONG, "LNS value '" + req.value + "' is too long"};
std::string reason;
std::optional<uint8_t> hf_version = m_wallet->get_hard_fork_version();
if (!hf_version) throw wallet_rpc_error{error_code::HF_QUERY_FAILED, tools::ERR_MSG_NETWORK_VERSION_QUERY_FAILED};
lns::mapping_type type;
if (!lns::validate_mapping_type(req.type, *hf_version, lns::lns_tx_type::lookup, &type, &reason))
throw wallet_rpc_error{error_code::WRONG_LNS_TYPE, "Wrong lns type given=" + reason};
if (!lns::validate_lns_name(type, req.name, &reason))
throw wallet_rpc_error{error_code::LNS_BAD_NAME, "Invalid LNS name '" + req.name + "': " + reason};
lns::mapping_value value;
if (!lns::mapping_value::validate(m_wallet->nettype(), type, req.value, &value, &reason))
throw wallet_rpc_error{error_code::LNS_BAD_VALUE, "Invalid LNS value '" + req.value + "': " + reason};
bool old_argon2 = type == lns::mapping_type::session && *hf_version < cryptonote::network_version_16;
if (!value.encrypt(req.name, nullptr, old_argon2))
throw wallet_rpc_error{error_code::LNS_VALUE_ENCRYPT_FAILED, "Value encryption failure"};
return {lokimq::to_hex(value.to_view())};
}
std::unique_ptr<tools::wallet2> wallet_rpc_server::load_wallet()
{
std::unique_ptr<tools::wallet2> wal;

View File

@ -161,6 +161,7 @@ namespace tools
wallet_rpc::LNS_MAKE_UPDATE_SIGNATURE::response invoke(wallet_rpc::LNS_MAKE_UPDATE_SIGNATURE::request&& req);
wallet_rpc::LNS_HASH_NAME::response invoke(wallet_rpc::LNS_HASH_NAME::request&& req);
wallet_rpc::LNS_DECRYPT_VALUE::response invoke(wallet_rpc::LNS_DECRYPT_VALUE::request&& req);
wallet_rpc::LNS_ENCRYPT_VALUE::response invoke(wallet_rpc::LNS_ENCRYPT_VALUE::request&& req);
wallet_rpc::QUERY_KEY::response invoke(wallet_rpc::QUERY_KEY::request&& req);
private:

View File

@ -1178,7 +1178,7 @@ KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_MAKE_UPDATE_SIGNATURE::request)
KV_SERIALIZE(type);
KV_SERIALIZE(name);
KV_SERIALIZE(value);
KV_SERIALIZE(encrypted_value);
KV_SERIALIZE(owner);
KV_SERIALIZE(backup_owner);
KV_SERIALIZE_MAP_CODE_END()
@ -1211,4 +1211,15 @@ KV_SERIALIZE_MAP_CODE_BEGIN(LNS_DECRYPT_VALUE::response)
KV_SERIALIZE(value)
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_ENCRYPT_VALUE::request)
KV_SERIALIZE(name);
KV_SERIALIZE(type);
KV_SERIALIZE(value);
KV_SERIALIZE_MAP_CODE_END()
KV_SERIALIZE_MAP_CODE_BEGIN(LNS_ENCRYPT_VALUE::response)
KV_SERIALIZE(encrypted_value)
KV_SERIALIZE_MAP_CODE_END()
}

View File

@ -2294,7 +2294,7 @@ This command is only required if the open wallet is one of the owners of a LNS r
{
std::string type; // The mapping type, currently we only support "session". In future "lokinet" and "blockchain" mappings will be available.
std::string name; // The desired name to update via Loki Name Service
std::string value; // (Optional): The new value that the name maps to via Loki Name Service. If not specified or given the empty string "", then the mapping's value remains unchanged.
std::string encrypted_value; // (Optional): The new encrypted value that the name maps to via Loki Name Service. If not specified or given the empty string "", then the mapping's value remains unchanged.
std::string owner; // (Optional): The new owner of the mapping. If not specified or given the empty string "", then the mapping's owner remains unchanged.
std::string backup_owner; // (Optional): The new backup owner of the mapping. If not specified or given the empty string "", then the mapping's backup owner remains unchanged.
uint32_t account_index; // (Optional) Use this wallet's subaddress account for generating the signature
@ -2333,16 +2333,39 @@ This command is only required if the open wallet is one of the owners of a LNS r
};
LOKI_RPC_DOC_INTROSPECT
// Takes a LNS encrypted value and decrypts the mapping value.
// Takes a LNS encrypted value and encrypts the mapping value using the LNS name.
struct LNS_ENCRYPT_VALUE : RPC_COMMAND
{
static constexpr auto names() { return NAMES("lns_encrypt_value"); }
struct request
{
std::string name; // The LNS name with which to encrypt the value.
std::string type; // The mapping type: "session" or "lokinet".
std::string value; // The value to be encrypted.
KV_MAP_SERIALIZABLE
};
struct response
{
std::string encrypted_value; // The encrypted value, in hex
KV_MAP_SERIALIZABLE
};
};
LOKI_RPC_DOC_INTROSPECT
// Takes a LNS encrypted value and decrypts the mapping value using the LNS name.
struct LNS_DECRYPT_VALUE : RPC_COMMAND
{
static constexpr auto names() { return NAMES("lns_decrypt_value"); }
struct request
{
std::string name; // The desired name to hash
std::string type; // The mapping type, currently we only support "session". In future "lokinet" and "blockchain" mappings will be available.
std::string encrypted_value; // The encrypted value represented in hex
std::string name; // The LNS name of the given encrypted value.
std::string type; // The mapping type: "session" or "lokinet".
std::string encrypted_value; // The encrypted value represented in hex.
KV_MAP_SERIALIZABLE
};
@ -2453,7 +2476,8 @@ This command is only required if the open wallet is one of the owners of a LNS r
LNS_UPDATE_MAPPING,
LNS_MAKE_UPDATE_SIGNATURE,
LNS_HASH_NAME,
LNS_DECRYPT_VALUE
LNS_DECRYPT_VALUE,
LNS_ENCRYPT_VALUE
>;
}

View File

@ -88,5 +88,7 @@ constexpr int16_t LNS_VALUE_TOO_LONG = -1004;
constexpr int16_t LNS_VALUE_NOT_HEX = -1005;
constexpr int16_t LNS_VALUE_LENGTH_NOT_EVEN = -1006;
constexpr int16_t LNS_VALUE_DECRYPT_FAILED = -1007;
constexpr int16_t LNS_VALUE_ENCRYPT_FAILED = -1008;
constexpr int16_t LNS_BAD_VALUE = -1009;
}

View File

@ -41,6 +41,7 @@
#include "console_handler.h"
#include "common/rules.h"
#include "cryptonote_config.h"
#include "p2p/net_node.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/cryptonote_basic_impl.h"
@ -554,8 +555,8 @@ cryptonote::transaction loki_chain_generator::create_loki_name_system_tx(crypton
if (lns::mapping_record mapping = lns_db_->get_mapping(type, name_base64_hash))
prev_txid = mapping.txid;
lns::mapping_value encrypted_value = {};
bool encrypted = lns::encrypt_mapping_value(name, value, encrypted_value);
lns::mapping_value encrypted_value = value;
bool encrypted = encrypted_value.encrypt(name, &name_hash, hf_version <= cryptonote::network_version_15_lns);
assert(encrypted);
std::vector<uint8_t> extra;
@ -593,8 +594,13 @@ cryptonote::transaction loki_chain_generator::create_loki_name_system_tx_update(
lns::mapping_value encrypted_value = {};
if (value)
{
bool encrypted = lns::encrypt_mapping_value(name, *value, encrypted_value);
if (use_asserts) assert(encrypted);
encrypted_value = *value;
if (!encrypted_value.encrypted)
{
assert(!signature); // Can't specify a signature with an unencrypted value because encrypting generates a new nonce and would invalidate it
bool encrypted = encrypted_value.encrypt(name, &name_hash, hf_version <= cryptonote::network_version_15_lns);
if (use_asserts) assert(encrypted);
}
}
lns::generic_signature signature_ = {};

View File

@ -142,6 +142,7 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY(loki_name_system_name_value_max_lengths);
GENERATE_AND_PLAY(loki_name_system_update_mapping_after_expiry_fails);
GENERATE_AND_PLAY(loki_name_system_update_mapping);
GENERATE_AND_PLAY(loki_name_system_update_mapping_argon2);
GENERATE_AND_PLAY(loki_name_system_update_mapping_multiple_owners);
GENERATE_AND_PLAY(loki_name_system_update_mapping_non_existent_name_fails);
GENERATE_AND_PLAY(loki_name_system_update_mapping_invalid_signature);

View File

@ -29,7 +29,10 @@
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
#include "loki_tests.h"
#include "cryptonote_config.h"
#include "cryptonote_core/loki_name_system.h"
#include "cryptonote_core/service_node_list.h"
#include "loki_economy.h"
extern "C"
{
@ -1049,14 +1052,6 @@ bool loki_core_test_state_change_ip_penalty_disallow_dupes::generate(std::vector
return true;
}
static lns::mapping_value helper_encrypt_lns_value(std::string const &name, lns::mapping_value const &value)
{
lns::mapping_value result;
bool encrypted = lns::encrypt_mapping_value(name, value, result);
assert(encrypted);
return result;
}
static bool verify_lns_mapping_record(char const *perr_context,
lns::mapping_record const &record,
lns::mapping_type type,
@ -1069,11 +1064,12 @@ static bool verify_lns_mapping_record(char const *perr_context,
lns::generic_owner const &owner,
lns::generic_owner const &backup_owner)
{
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, value);
CHECK_EQ(record.loaded, true);
CHECK_EQ(record.type, type);
CHECK_EQ(record.name_hash, lns::name_to_base64_hash(name));
CHECK_EQ(record.encrypted_value, encrypted_value);
lns::mapping_value decrypted{record.encrypted_value};
CHECK_EQ(decrypted.decrypt(name, type), true);
CHECK_EQ(decrypted, value);
CHECK_EQ(record.register_height, register_height);
CHECK_EQ(record.update_height, update_height);
CHECK_EQ(record.txid, txid);
@ -1558,7 +1554,7 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
valid_data.fields |= lns::extra_field::buy_no_backup;
valid_data.owner = miner_key.owner;
valid_data.type = lns::mapping_type::wallet;
valid_data.encrypted_value = helper_encrypt_lns_value(name, miner_key.wallet_value).to_string();
valid_data.encrypted_value = miner_key.wallet_value.make_encrypted(name).to_string();
valid_data.name_hash = lns::name_to_hash(name);
if (lns::mapping_type_allowed(gen.hardfork(), lns::mapping_type::wallet))
@ -1568,14 +1564,14 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.name_hash = {};
data.encrypted_value = helper_encrypt_lns_value("", miner_key.wallet_value).to_string();
data.encrypted_value = miner_key.wallet_value.make_encrypted("").to_string();
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Blockchain) Empty wallet name in LNS is invalid");
}
// Blockchain value (wallet address) is invalid, too short
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.wallet_value).to_string();
data.encrypted_value = miner_key.wallet_value.make_encrypted(name).to_string();
data.encrypted_value.resize(data.encrypted_value.size() - 1);
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Blockchain) Wallet value in LNS too long");
}
@ -1583,7 +1579,7 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
// Blockchain value (wallet address) is invalid, too long
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.wallet_value).to_string();
data.encrypted_value = miner_key.wallet_value.make_encrypted(name).to_string();
data.encrypted_value.resize(data.encrypted_value.size() + 1);
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Blockchain) Wallet value in LNS too long");
}
@ -1596,14 +1592,14 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.name_hash = {};
data.encrypted_value = helper_encrypt_lns_value("", miner_key.lokinet_value).to_string();
data.encrypted_value = miner_key.lokinet_value.make_encrypted("").to_string();
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Lokinet) Empty domain name in LNS is invalid");
}
// Lokinet value too short
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.lokinet_value).to_string();
data.encrypted_value = miner_key.lokinet_value.make_encrypted(name).to_string();
data.encrypted_value.resize(data.encrypted_value.size() - 1);
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Lokinet) Domain value in LNS too long");
}
@ -1611,7 +1607,7 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
// Lokinet value too long
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.lokinet_value).to_string();
data.encrypted_value = miner_key.lokinet_value.make_encrypted(name).to_string();
data.encrypted_value.resize(data.encrypted_value.size() + 1);
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Lokinet) Domain value in LNS too long");
}
@ -1624,7 +1620,7 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
valid_data.name_hash = lns::name_to_hash(name);
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.session_value).to_string();
data.encrypted_value = miner_key.session_value.make_encrypted(name).to_string();
data.encrypted_value.resize(data.encrypted_value.size() - 1);
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Session) User id, value too short");
}
@ -1632,7 +1628,7 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
// Session value too long
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.session_value).to_string();
data.encrypted_value = miner_key.session_value.make_encrypted(name).to_string();
data.encrypted_value.resize(data.encrypted_value.size() + 1);
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Session) User id, value too long");
}
@ -1641,7 +1637,7 @@ bool loki_name_system_invalid_tx_extra_params::generate(std::vector<test_event_e
{
cryptonote::tx_extra_loki_name_system data = valid_data;
data.name_hash = {};
data.encrypted_value = helper_encrypt_lns_value("", miner_key.session_value).to_string();
data.encrypted_value = miner_key.session_value.make_encrypted("").to_string();
make_lns_tx_with_custom_extra(gen, events, miner, data, false, "(Session) Name empty");
}
}
@ -1984,7 +1980,7 @@ bool loki_name_system_name_value_max_lengths::generate(std::vector<test_event_en
std::string name(lns::WALLET_NAME_MAX, 'A');
data.type = lns::mapping_type::wallet;
data.name_hash = lns::name_to_hash(name);
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.wallet_value).to_string();
data.encrypted_value = miner_key.wallet_value.make_encrypted(name).to_string();
make_lns_tx_with_custom_extra(gen, events, miner, data);
}
@ -2001,7 +1997,7 @@ bool loki_name_system_name_value_max_lengths::generate(std::vector<test_event_en
data.type = lns::mapping_type::lokinet_1year;
data.name_hash = lns::name_to_hash(name);
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.lokinet_value).to_string();
data.encrypted_value = miner_key.lokinet_value.make_encrypted(name).to_string();
make_lns_tx_with_custom_extra(gen, events, miner, data);
}
@ -2010,7 +2006,7 @@ bool loki_name_system_name_value_max_lengths::generate(std::vector<test_event_en
std::string name(lns::SESSION_DISPLAY_NAME_MAX, 'A');
data.type = lns::mapping_type::session;
data.name_hash = lns::name_to_hash(name);
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.session_value).to_string();
data.encrypted_value = miner_key.session_value.make_encrypted(name).to_string();
make_lns_tx_with_custom_extra(gen, events, miner, data);
}
@ -2068,9 +2064,11 @@ bool loki_name_system_update_mapping_after_expiry_fails::generate(std::vector<te
return true;
}
uint8_t loki_name_system_update_mapping::hf() { return cryptonote::network_version_count - 1; }
uint8_t loki_name_system_update_mapping_argon2::hf() { return cryptonote::network_version_15_lns; }
bool loki_name_system_update_mapping::generate(std::vector<test_event_entry> &events)
{
std::vector<std::pair<uint8_t, uint64_t>> hard_forks = loki_generate_sequential_hard_fork_table();
std::vector<std::pair<uint8_t, uint64_t>> hard_forks = loki_generate_sequential_hard_fork_table(hf());
loki_chain_generator gen(events, hard_forks);
gen.add_blocks_until_version(hard_forks.back().first);
gen.add_mined_money_unlock_blocks();
@ -2083,7 +2081,7 @@ bool loki_name_system_update_mapping::generate(std::vector<test_event_entry> &ev
crypto::hash session_tx_hash1;
std::string session_name1 = "MyName";
{
cryptonote::transaction tx1 = gen.create_and_add_loki_name_system_tx(miner, lns::mapping_type::session, session_name1, miner_key.session_value);
cryptonote::transaction tx1 = gen.create_and_add_loki_name_system_tx(miner, gen.hardfork(), lns::mapping_type::session, session_name1, miner_key.session_value);
session_tx_hash1 = cryptonote::get_transaction_hash(tx1);
gen.create_and_add_next_block({tx1});
}
@ -2103,14 +2101,14 @@ bool loki_name_system_update_mapping::generate(std::vector<test_event_entry> &ev
});
// Test update mapping with same name fails
{
cryptonote::transaction tx1 = gen.create_loki_name_system_tx_update(miner, lns::mapping_type::session, session_name1, &miner_key.session_value);
if (hf() == cryptonote::network_version_15_lns) {
cryptonote::transaction tx1 = gen.create_loki_name_system_tx_update(miner, gen.hardfork(), lns::mapping_type::session, session_name1, &miner_key.session_value);
gen.add_tx(tx1, false /*can_be_added_to_blockchain*/, "Can not add a LNS TX that re-updates the underlying value to same value");
}
crypto::hash session_tx_hash2;
{
cryptonote::transaction tx1 = gen.create_and_add_loki_name_system_tx_update(miner, lns::mapping_type::session, session_name1, &bob_key.session_value);
cryptonote::transaction tx1 = gen.create_and_add_loki_name_system_tx_update(miner, gen.hardfork(), lns::mapping_type::session, session_name1, &bob_key.session_value);
session_tx_hash2 = cryptonote::get_transaction_hash(tx1);
gen.create_and_add_next_block({tx1});
}
@ -2174,7 +2172,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
// Update with owner1
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_ed25519_signature(hash, owner1_key);
@ -2196,7 +2194,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
// Update with owner2
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_ed25519_signature(hash, owner2_key);
@ -2234,7 +2232,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
// Update with owner1
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_monero_signature(hash, owner1.wallet.address.m_spend_public_key, account1.get_keys().m_spend_secret_key);
@ -2256,7 +2254,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
// Update with owner2
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_monero_signature(hash, owner2.wallet.address.m_spend_public_key, account2.get_keys().m_spend_secret_key);
@ -2298,7 +2296,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
// Update with owner1
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_ed25519_signature(hash, owner1_key);
@ -2320,7 +2318,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
// Update with owner2
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_monero_signature(hash, owner2.wallet.address.m_spend_public_key, account2.get_keys().m_spend_secret_key);
@ -2362,7 +2360,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_monero_signature(hash, owner1.wallet.address.m_spend_public_key, account1.get_keys().m_spend_secret_key);
@ -2385,7 +2383,7 @@ bool loki_name_system_update_mapping_multiple_owners::generate(std::vector<test_
{
lns_keys_t temp_keys = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = helper_encrypt_lns_value(name, temp_keys.session_value);
lns::mapping_value encrypted_value = temp_keys.session_value.make_encrypted(name);
crypto::hash hash = lns::tx_extra_signature_hash(encrypted_value.to_view(), nullptr /*owner*/, nullptr /*backup_owner*/, txid);
auto signature = lns::make_ed25519_signature(hash, owner2_key);
@ -2437,6 +2435,7 @@ bool loki_name_system_update_mapping_invalid_signature::generate(std::vector<tes
gen.create_and_add_next_block({tx1});
lns_keys_t bob_key = make_lns_keys(gen.add_account());
lns::mapping_value encrypted_value = bob_key.session_value.make_encrypted(name);
lns::generic_signature invalid_signature = {};
cryptonote::transaction tx2 = gen.create_loki_name_system_tx_update(miner, lns::mapping_type::session, name, &bob_key.session_value, nullptr /*owner*/, nullptr /*backup_owner*/, &invalid_signature, false /*use_asserts*/);
gen.add_tx(tx2, false /*can_be_added_to_blockchain*/, "Can not add a updating LNS TX with an invalid signature");
@ -2567,7 +2566,7 @@ bool loki_name_system_wrong_version::generate(std::vector<test_event_entry> &eve
data.owner = miner_key.owner;
data.type = lns::mapping_type::session;
data.name_hash = lns::name_to_hash(name);
data.encrypted_value = helper_encrypt_lns_value(name, miner_key.session_value).to_string();
data.encrypted_value = miner_key.session_value.make_encrypted(name).to_string();
uint64_t new_height = cryptonote::get_block_height(gen.top().block) + 1;
uint8_t new_hf_version = gen.get_hf_version_at(new_height);

View File

@ -64,7 +64,8 @@ struct loki_name_system_large_reorg
struct loki_name_system_name_renewal : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct loki_name_system_name_value_max_lengths : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct loki_name_system_update_mapping_after_expiry_fails : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct loki_name_system_update_mapping : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct loki_name_system_update_mapping : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); virtual uint8_t hf(); };
struct loki_name_system_update_mapping_argon2 : public loki_name_system_update_mapping { uint8_t hf() override; };
struct loki_name_system_update_mapping_multiple_owners : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct loki_name_system_update_mapping_non_existent_name_fails : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };
struct loki_name_system_update_mapping_invalid_signature : public test_chain_unit_base { bool generate(std::vector<test_event_entry>& events); };

View File

@ -2,6 +2,7 @@
#include "common/loki.h"
#include "cryptonote_core/loki_name_system.h"
#include "loki_economy.h"
TEST(loki_name_system, name_tests)
{
@ -79,33 +80,59 @@ TEST(loki_name_system, value_encrypt_and_decrypt)
value.len = 32;
memset(&value.buffer[0], 'a', value.len);
// The type here is not hugely important for decryption except that lokinet (as opposed to
// session) doesn't fall back to argon2 decryption if decryption fails.
constexpr auto type = lns::mapping_type::lokinet_1year;
// Encryption and Decryption success
{
lns::mapping_value encrypted_value = {};
lns::mapping_value decrypted_value = {};
ASSERT_TRUE(lns::encrypt_mapping_value(name, value, encrypted_value));
ASSERT_TRUE(lns::decrypt_mapping_value(name, encrypted_value, decrypted_value));
ASSERT_TRUE(value == decrypted_value);
auto mval = value;
ASSERT_TRUE(mval.encrypt(name));
ASSERT_FALSE(mval == value);
ASSERT_TRUE(mval.decrypt(name, type));
ASSERT_TRUE(mval == value);
}
// Decryption Fail: Encrypted value was modified
{
lns::mapping_value encrypted_value = {};
ASSERT_TRUE(lns::encrypt_mapping_value(name, value, encrypted_value));
auto mval = value;
ASSERT_FALSE(mval.encrypted);
ASSERT_TRUE(mval.encrypt(name));
ASSERT_TRUE(mval.encrypted);
encrypted_value.buffer[0] = 'Z';
lns::mapping_value decrypted_value;
ASSERT_FALSE(lns::decrypt_mapping_value(name, encrypted_value, decrypted_value));
mval.buffer[0] = 'Z';
ASSERT_FALSE(mval.decrypt(name, type));
ASSERT_TRUE(mval.encrypted);
}
// Decryption Fail: Name was modified
{
std::string name_copy = name;
lns::mapping_value encrypted_value = {};
ASSERT_TRUE(lns::encrypt_mapping_value(name_copy, value, encrypted_value));
auto mval = value;
ASSERT_TRUE(mval.encrypt(name_copy));
name_copy[0] = 'Z';
lns::mapping_value decrypted_value;
ASSERT_FALSE(lns::decrypt_mapping_value(name_copy, encrypted_value, decrypted_value));
ASSERT_FALSE(mval.decrypt(name_copy, type));
}
}
TEST(loki_name_system, value_encrypt_and_decrypt_heavy)
{
std::string name = "abcdefg";
lns::mapping_value value = {};
value.len = 33;
memset(&value.buffer[0], 'a', value.len);
// Encryption and Decryption success for the older argon2-based encryption key
{
auto mval = value;
auto mval_new = value;
ASSERT_TRUE(mval.encrypt(name, nullptr, true));
ASSERT_TRUE(mval_new.encrypt(name, nullptr, false));
ASSERT_EQ(mval.len + 24, mval_new.len); // New value appends a 24-byte nonce
ASSERT_TRUE(mval.decrypt(name, lns::mapping_type::session));
ASSERT_TRUE(mval_new.decrypt(name, lns::mapping_type::session));
ASSERT_TRUE(mval == value);
ASSERT_TRUE(mval_new == value);
}
}