Merge branch 'secp256k1' into develop

This commit is contained in:
sowle 2024-09-24 02:14:12 +02:00
commit f17dba64ce
No known key found for this signature in database
GPG key ID: C07A24B2D89D49FC
40 changed files with 1778 additions and 262 deletions

3
.gitmodules vendored
View file

@ -12,3 +12,6 @@
[submodule "contrib/jwt-cpp"]
path = contrib/jwt-cpp
url = https://github.com/Thalhammer/jwt-cpp.git
[submodule "contrib/bitcoin-secp256k1"]
path = contrib/bitcoin-secp256k1
url = https://github.com/bitcoin-core/secp256k1.git

View file

@ -5,6 +5,14 @@ add_subdirectory(zlib)
add_subdirectory(db)
add_subdirectory(ethereum)
option(SECP256K1_BUILD_BENCHMARK "Build benchmarks." OFF)
option(SECP256K1_BUILD_TESTS "Build tests." OFF)
option(SECP256K1_BUILD_EXHAUSTIVE_TESTS "Build exhaustive tests." OFF)
option(SECP256K1_BUILD_CTIME_TESTS "Build constant-time tests." OFF)
option(SECP256K1_BUILD_EXAMPLES "Build examples." OFF)
set_property(GLOBAL PROPERTY CTEST_TARGETS_ADDED 1)
add_subdirectory(bitcoin-secp256k1)
if( NOT DISABLE_TOR)
add_subdirectory(tor-connect)
endif()
@ -23,6 +31,9 @@ set_property(TARGET libminiupnpc-static PROPERTY FOLDER "contrib")
set_property(TARGET zlibstatic PROPERTY FOLDER "contrib")
set_property(TARGET mdbx PROPERTY FOLDER "contrib")
set_property(TARGET lmdb PROPERTY FOLDER "contrib")
set_property(TARGET secp256k1 PROPERTY FOLDER "contrib")
set_property(TARGET secp256k1_precomputed PROPERTY FOLDER "contrib")
if( NOT DISABLE_TOR)
set_property(TARGET tor-connect PROPERTY FOLDER "contrib")
endif()

@ -0,0 +1 @@
Subproject commit a5269373fa13ff845f654d81b90629dd78495641

View file

@ -84,9 +84,9 @@ namespace misc_utils
{
template<typename t_type_a, typename t_type_b>
void cast_assign_a_to_b(t_type_a& a, const t_type_b& b)
void cast_assign_a_to_b(const t_type_a& a, t_type_b& b)
{
*static_cast<t_type_b*>(&a) = b;
*static_cast<t_type_a*>(&b) = a;
}
template<class _Ty1,

View file

@ -1,3 +1,4 @@
// Copyright (c) 2024, Zano Project
// Copyright (c) 2006-2013, Andrey N. Sabelnikov, www.sabelnikov.net
// All rights reserved.
//
@ -473,6 +474,29 @@ namespace epee
return r;
}
//-------------------------------------------------------------------------------------------------------------------
//std::optional
template<class t_type, class t_storage>
bool kv_serialize(const std::optional<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname)
{
if(d.has_value())
{
return kv_serialize(*d, stg, hparent_section, pname);
}
return true;
}
//-------------------------------------------------------------------------------------------------------------------
template<class t_type, class t_storage>
bool kv_unserialize(std::optional<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname)
{
d = t_type{};
bool r = kv_unserialize(*d, stg, hparent_section, pname);
if (!r)
{
d = std::nullopt;
}
return r;
}
//-------------------------------------------------------------------------------------------------------------------
//boost::shared_ptr
template<class t_type, class t_storage>
bool kv_serialize(const boost::shared_ptr<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname)
@ -496,6 +520,30 @@ namespace epee
}
return r;
}
//-------------------------------------------------------------------------------------------------------------------
//std::shared_ptr
template<class t_type, class t_storage>
bool kv_serialize(const std::shared_ptr<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname)
{
if (d.get())
{
return kv_serialize(*d, stg, hparent_section, pname);
}
return true;
}
//-------------------------------------------------------------------------------------------------------------------
template<class t_type, class t_storage>
bool kv_unserialize(std::shared_ptr<t_type>& d, t_storage& stg, typename t_storage::hsection hparent_section, const char* pname)
{
d.reset();
t_type* ptr = new t_type();
bool r = kv_unserialize(*ptr, stg, hparent_section, pname);
if (!r)
{
d.reset(ptr);
}
return r;
}
}
}

View file

@ -116,6 +116,8 @@ else()
endif()
add_library(crypto ${CRYPTO})
add_dependencies(crypto secp256k1)
target_link_libraries(crypto secp256k1)
add_library(currency_core ${CURRENCY_CORE})
add_dependencies(currency_core version ${PCH_LIB_NAME})

View file

@ -4,7 +4,7 @@
#pragma once
#define BEGIN_BOOST_SERIALIZATION() template <class t_archive> inline void serialize(t_archive &_arch, const unsigned int ver) {
#define BEGIN_BOOST_SERIALIZATION() template <class t_archive> void serialize(t_archive &_arch, const unsigned int ver) {
template<size_t A, size_t B> struct TAssertEquality {
static_assert(A == B, "Serialization map is not updated, sizeof() missmatch");

View file

@ -21,6 +21,7 @@
#include "crypto/clsag.h"
#include "crypto/zarcanum.h"
#include "crypto/one_out_of_many_proofs.h"
#include "crypto/eth_signature.h"
#include "boost_serialization_maps.h"
#include "serialization/keyvalue_enable_POD_serialize_as_string.h"
//
@ -230,6 +231,8 @@ BLOB_SERIALIZER(crypto::key_image);
BLOB_SERIALIZER(crypto::signature);
BLOB_SERIALIZER(crypto::scalar_t);
BLOB_SERIALIZER(crypto::point_t);
BLOB_SERIALIZER(crypto::eth_public_key);
BLOB_SERIALIZER(crypto::eth_signature);
VARIANT_TAG(debug_archive, crypto::hash, "hash");
VARIANT_TAG(debug_archive, crypto::public_key, "public_key");
@ -237,6 +240,8 @@ VARIANT_TAG(debug_archive, crypto::secret_key, "secret_key");
VARIANT_TAG(debug_archive, crypto::key_derivation, "key_derivation");
VARIANT_TAG(debug_archive, crypto::key_image, "key_image");
VARIANT_TAG(debug_archive, crypto::signature, "signature");
VARIANT_TAG(debug_archive, crypto::eth_public_key, "eth_public_key");
VARIANT_TAG(debug_archive, crypto::eth_signature, "eth_signature");
//
@ -245,6 +250,8 @@ VARIANT_TAG(debug_archive, crypto::signature, "signature");
KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::scalar_t);
KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::hash);
KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::eth_public_key);
KV_ENABLE_POD_SERIALIZATION_AS_HEX(crypto::eth_signature);
//
// Boost serialization
@ -296,5 +303,15 @@ namespace boost
{
a & reinterpret_cast<char (&)[sizeof(crypto::point_t)]>(x);
}
template <class Archive>
inline void serialize(Archive &a, crypto::eth_public_key &x, const boost::serialization::version_type ver)
{
a & reinterpret_cast<char (&)[sizeof(crypto::eth_public_key)]>(x);
}
template <class Archive>
inline void serialize(Archive &a, crypto::eth_signature &x, const boost::serialization::version_type ver)
{
a & reinterpret_cast<char (&)[sizeof(crypto::eth_signature)]>(x);
}
} // namespace serialization
} // namespace boost

View file

@ -8,6 +8,7 @@
#include <string>
#include <boost/multiprecision/cpp_int.hpp>
#include "crypto.h"
#include "eth_signature.h"
namespace crypto
{
@ -1209,6 +1210,16 @@ namespace crypto
m_elements.emplace_back(pk);
}
void add_eth_pub_key(const crypto::eth_public_key& epk)
{
static_assert(sizeof(item_t) == 32, "unexpected size of hs_t::item_t");
static_assert(sizeof epk.data == 33, "unexpected size of eth_public_key");
m_elements.emplace_back(c_scalar_0);
m_elements.emplace_back(c_scalar_0);
char* p = m_elements[m_elements.size() - 2].c; // pointer to the first of the two added items
memcpy(p, &epk.data, sizeof epk.data);
}
void add_key_image(const crypto::key_image& ki)
{
m_elements.emplace_back(ki);

View file

@ -0,0 +1,162 @@
// Copyright (c) 2024 Zano Project
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "eth_signature.h"
#include "crypto.h"
#include "bitcoin-secp256k1/include/secp256k1.h"
#include "random.h"
#include "misc_language.h"
#include <string_tools.h>
namespace crypto
{
bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept
{
try
{
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
secp256k1_context_destroy(ctx);
ctx = nullptr;
});
uint8_t randomness[32];
crypto::generate_random_bytes(sizeof randomness, randomness);
if (!secp256k1_context_randomize(ctx, randomness))
return false;
for(size_t i = 1024; i != 0; --i)
{
crypto::generate_random_bytes(sizeof sec_key, sec_key.data);
if (secp256k1_ec_seckey_verify(ctx, sec_key.data))
break;
if (i == 1)
return false;
}
secp256k1_pubkey uncompressed_pub_key{};
if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data))
return false;
size_t output_len = sizeof pub_key;
if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED))
return false;
return true;
}
catch(...)
{
return false;
}
}
bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept
{
try
{
// TODO: do we need this? consider using static context
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
secp256k1_context_destroy(ctx);
ctx = nullptr;
});
secp256k1_pubkey uncompressed_pub_key{};
if (!secp256k1_ec_pubkey_create(ctx, &uncompressed_pub_key, sec_key.data))
return false;
size_t output_len = sizeof pub_key;
if (!secp256k1_ec_pubkey_serialize(ctx, pub_key.data, &output_len, &uncompressed_pub_key, SECP256K1_EC_COMPRESSED))
return false;
return true;
}
catch(...)
{
return false;
}
}
// generates secp256k1 ECDSA signature
bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept
{
try
{
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
secp256k1_context_destroy(ctx);
ctx = nullptr;
});
uint8_t randomness[32];
crypto::generate_random_bytes(sizeof randomness, randomness);
if (!secp256k1_context_randomize(ctx, randomness))
return false;
secp256k1_ecdsa_signature secp256k1_ecdsa_sig{};
if (!secp256k1_ecdsa_sign(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, sec_key.data, NULL, NULL))
return false;
if (!secp256k1_ecdsa_signature_serialize_compact(ctx, sig.data, &secp256k1_ecdsa_sig))
return false;
return true;
}
catch(...)
{
return false;
}
}
// verifies secp256k1 ECDSA signature
bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept
{
try
{
// TODO (performance) consider using secp256k1_context_static for verification -- sowle
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
auto slh = epee::misc_utils::create_scope_leave_handler([&ctx](){
secp256k1_context_destroy(ctx);
ctx = nullptr;
});
uint8_t randomness[32];
crypto::generate_random_bytes(sizeof randomness, randomness);
if (!secp256k1_context_randomize(ctx, randomness))
return false;
secp256k1_ecdsa_signature secp256k1_ecdsa_sig{};
secp256k1_pubkey uncompressed_pub_key{};
if (!secp256k1_ecdsa_signature_parse_compact(ctx, &secp256k1_ecdsa_sig, sig.data))
return false;
if (!secp256k1_ec_pubkey_parse(ctx, &uncompressed_pub_key, pub_key.data, sizeof pub_key))
return false;
// verify a signature
if (!secp256k1_ecdsa_verify(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)m.data, &uncompressed_pub_key))
return false;
return true;
}
catch(...)
{
return false;
}
}
std::ostream& operator<<(std::ostream& o, const eth_secret_key& v)
{
return o << epee::string_tools::pod_to_hex(v);
}
std::ostream& operator<<(std::ostream& o, const eth_public_key& v)
{
return o << epee::string_tools::pod_to_hex(v);
}
} // namespace crypto

View file

@ -0,0 +1,66 @@
// Copyright (c) 2024 Zano Project
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#pragma once
#include <cstdint>
#include <iosfwd>
#include "hash.h"
namespace crypto
{
// secp256k1 public key in serialized (compressed) form that is used in Etherium
struct eth_public_key
{
uint8_t data[33];
};
// secp256k1 secret key
struct eth_secret_key
{
uint8_t data[32];
};
// secp256k1 ECDSA signature is serialized (compressed) form that is used in Etherium
struct eth_signature
{
uint8_t data[64];
};
// generates secp256k1 keypair
bool generate_eth_key_pair(eth_secret_key& sec_key, eth_public_key& pub_key) noexcept;
// converts eth_secret_key to eth_public_key
bool eth_secret_key_to_public_key(const eth_secret_key& sec_key, eth_public_key& pub_key) noexcept;
// generates secp256k1 ECDSA signature
bool generate_eth_signature(const hash& m, const eth_secret_key& sec_key, eth_signature& sig) noexcept;
// verifies secp256k1 ECDSA signature
bool verify_eth_signature(const hash& m, const eth_public_key& pub_key, const eth_signature& sig) noexcept;
inline bool operator==(const eth_public_key& lhs, const eth_public_key& rhs)
{
return memcmp(lhs.data, rhs.data, sizeof lhs.data) == 0;
}
inline bool operator!=(const eth_public_key& lhs, const eth_public_key& rhs)
{
return !(lhs == rhs);
}
inline bool operator==(const eth_secret_key& lhs, const eth_secret_key& rhs)
{
return memcmp(lhs.data, rhs.data, sizeof lhs.data) == 0;
}
inline bool operator!=(const eth_secret_key& lhs, const eth_secret_key& rhs)
{
return !(lhs == rhs);
}
std::ostream& operator<<(std::ostream& o, const eth_secret_key& v);
std::ostream& operator<<(std::ostream& o, const eth_public_key& v);
} // namespace crypto

View file

@ -123,6 +123,15 @@ namespace crypto
return generate_schnorr_sig(m, point_t(A), scalar_t(secret_a), result);
}
inline bool generate_schnorr_sig(const hash& m, const secret_key& secret_a, generic_schnorr_sig& result)
{
scalar_t secret_a_s(secret_a);
if (!secret_a_s.is_reduced())
return false;
point_t A = secret_a_s * c_point_G;
return generate_schnorr_sig_custom_generator(m, A, secret_a_s, result, c_point_G);
}
template<generator_tag gen = gt_G>
inline bool verify_schnorr_sig(const hash& m, const public_key& A, const generic_schnorr_sig& sig) noexcept;

View file

@ -4008,7 +4008,8 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr
//@@ remove get_tx_fee_median();
LOG_PRINT_MAGENTA("[ALIAS_REGISTERED]: " << ai.m_alias << ": " << get_account_address_as_str(ai.m_address) << ", fee median: " << get_tx_fee_median(), LOG_LEVEL_1);
rise_core_event(CORE_EVENT_ADD_ALIAS, alias_info_to_rpc_alias_info(ai));
}else
}
else
{
//update procedure
CHECK_AND_ASSERT_MES(ai.m_sign.size() == 1, false, "alias " << ai.m_alias << " can't be update, wrong ai.m_sign.size() count: " << ai.m_sign.size());
@ -4103,14 +4104,25 @@ bool blockchain_storage::pop_asset_info(const crypto::public_key& asset_id)
//------------------------------------------------------------------
bool validate_ado_ownership(asset_op_verification_context& avc)
{
asset_operation_ownership_proof aoop = AUTO_VAL_INIT(aoop);
bool r = get_type_in_variant_container(avc.tx.proofs, aoop);
CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed - missing signature (asset_operation_ownership_proof)");
bool r = false;
CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has empty history record");
const asset_descriptor_operation& last_ado = avc.asset_op_history->back();
CHECK_AND_ASSERT_MES(avc.asset_op_history->size() != 0, false, "asset with id " << avc.asset_id << " has invalid history size() == 0");
if (last_ado.descriptor.owner_eth_pub_key.has_value())
{
CHECK_AND_ASSERT_MES(last_ado.descriptor.owner == null_pkey, false, "owner_eth_pub_key is set but owner pubkey is nonzero");
asset_operation_ownership_proof_eth aoop_eth{};
r = get_type_in_variant_container(avc.tx.proofs, aoop_eth);
CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof_eth is missing");
return crypto::verify_eth_signature(avc.tx_id, last_ado.descriptor.owner_eth_pub_key.value(), aoop_eth.eth_sig);
}
crypto::public_key owner_key = avc.asset_op_history->back().descriptor.owner;
return crypto::verify_schnorr_sig(avc.tx_id, owner_key, aoop.gss);
// owner_eth_pub_key has no value -- fallback to default
asset_operation_ownership_proof aoop{};
r = get_type_in_variant_container(avc.tx.proofs, aoop);
CHECK_AND_ASSERT_MES(r, false, "Ownership validation failed: asset_operation_ownership_proof is missing");
return crypto::verify_schnorr_sig(avc.tx_id, last_ado.descriptor.owner, aoop.gss);
}
//------------------------------------------------------------------
bool blockchain_storage::validate_asset_operation_against_current_blochain_state(asset_op_verification_context& avc) const
@ -4122,11 +4134,12 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state
const asset_descriptor_operation& ado = avc.ado;
bool need_to_validate_ao_amount_commitment = true;
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER)
{
CHECK_AND_ASSERT_MES(!avc.asset_op_history, false, "asset with id " << avc.asset_id << " has already been registered");
avc.amount_to_validate = ado.descriptor.current_supply;
CHECK_AND_ASSERT_MES(validate_asset_operation_amount_commitment(avc), false, "validate_asset_operation_amount_commitment failed!");
if(this->is_hardfork_active(ZANO_HARDFORK_05))
{
CHECK_AND_ASSERT_MES(validate_ado_initial(ado.descriptor), false, "validate_ado_initial failed!");
@ -4134,48 +4147,48 @@ bool blockchain_storage::validate_asset_operation_against_current_blochain_state
}
else
{
CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size(), false, "asset with id " << avc.asset_id << " has not been registered");
CHECK_AND_ASSERT_MES(avc.asset_op_history && avc.asset_op_history->size() > 0, false, "asset with id " << avc.asset_id << " has not been registered");
const asset_descriptor_operation& last_ado = avc.asset_op_history->back();
// check ownership permission
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE /*|| ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN*/)
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
bool r = validate_ado_ownership(avc);
CHECK_AND_ASSERT_MES(r, false, "Faild to validate ownership of asset_descriptor_operation, rejecting");
CHECK_AND_ASSERT_MES(r, false, "Failed to validate ownership of asset_descriptor_operation, rejecting");
}
avc.amount_to_validate = 0;
bool need_to_validate_balance_proof = true;
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE)
{
//check that total current_supply haven't changed
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == avc.asset_op_history->back().descriptor.current_supply, false, "update operation attempted to change emission, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "update operation attempted to change fileds that shouldn't be modified, failed");
need_to_validate_balance_proof = false;
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply == last_ado.descriptor.current_supply, false, "update operation attempted to change emission, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "update operation modifies asset descriptor in a prohibited manner");
need_to_validate_ao_amount_commitment = false;
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT)
{
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > avc.asset_op_history->back().descriptor.current_supply, false, "emit operation does not increase the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "emit operation is not allowed to update fields");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == avc.asset_op_history->back().descriptor.meta_info, false, "emit operation is not allowed to update meta info");
avc.amount_to_validate = ado.descriptor.current_supply - avc.asset_op_history->back().descriptor.current_supply;
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply > last_ado.descriptor.current_supply, false, "emit operation does not increase the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "emit operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "emit operation is not allowed to update meta info");
avc.amount_to_validate = ado.descriptor.current_supply - last_ado.descriptor.current_supply;
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN)
{
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < avc.asset_op_history->back().descriptor.current_supply, false, "burn operation does not decrease the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, avc.asset_op_history->back().descriptor), false, "burn operation is not allowed to update fields");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == avc.asset_op_history->back().descriptor.meta_info, false, "burn operation is not allowed to update meta info");
avc.amount_to_validate = avc.asset_op_history->back().descriptor.current_supply - ado.descriptor.current_supply;
CHECK_AND_ASSERT_MES(ado.descriptor.current_supply < last_ado.descriptor.current_supply, false, "burn operation does not decrease the current supply, failed");
CHECK_AND_ASSERT_MES(validate_ado_update_allowed(ado.descriptor, last_ado.descriptor), false, "burn operation modifies asset descriptor in a prohibited manner");
CHECK_AND_ASSERT_MES(ado.descriptor.meta_info == last_ado.descriptor.meta_info, false, "burn operation is not allowed to update meta info");
avc.amount_to_validate = last_ado.descriptor.current_supply - ado.descriptor.current_supply;
}
else
{
LOG_ERROR("Unknown operation type: " << (int)ado.operation_type);
return false;
}
}
if (need_to_validate_balance_proof)
{
bool r = validate_asset_operation_amount_commitment(avc);
CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation");
}
if (need_to_validate_ao_amount_commitment)
{
bool r = validate_asset_operation_amount_commitment(avc);
CHECK_AND_ASSERT_MES(r, false, "Balance proof validation failed for asset_descriptor_operation");
}
return true;
@ -5932,6 +5945,10 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti
}
}
// TODO @#@#: add check for descriptor.owner_eth_pub_key
return true;
}
//------------------------------------------------------------------

View file

@ -40,6 +40,7 @@
#include "crypto/hash.h"
#include "crypto/range_proofs.h"
#include "crypto/zarcanum.h"
#include "crypto/eth_signature.h"
#include "misc_language.h"
#include "block_flags.h"
#include "etc_custom_serialization.h"
@ -695,6 +696,9 @@ namespace currency
}
};
#define ASSET_DESCRIPTOR_BASE_STRUCTURE_VER 1
typedef boost::variant<crypto::public_key, crypto::eth_public_key> asset_owner_pub_key_v;
struct asset_descriptor_base
{
@ -706,9 +710,11 @@ namespace currency
std::string meta_info;
crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8
bool hidden_supply = false;
uint8_t version = 0;
boost::optional<crypto::eth_public_key> owner_eth_pub_key; // note: the size is 33 bytes (if present) // NOTE: using boost::optional instead of std::optional because of the Boost compilation issue: https://github.com/boostorg/serialization/issues/319 -- sowle
BEGIN_VERSIONED_SERIALIZE(0, version)
uint8_t version = ASSET_DESCRIPTOR_BASE_STRUCTURE_VER;
BEGIN_VERSIONED_SERIALIZE(ASSET_DESCRIPTOR_BASE_STRUCTURE_VER, version)
FIELD(total_max_supply)
FIELD(current_supply)
FIELD(decimal_point)
@ -717,9 +723,10 @@ namespace currency
FIELD(meta_info)
FIELD(owner)
FIELD(hidden_supply)
END_VERSION_UNDER(1)
FIELD(owner_eth_pub_key)
END_SERIALIZE()
BEGIN_BOOST_SERIALIZATION()
BOOST_SERIALIZE(total_max_supply)
BOOST_SERIALIZE(current_supply)
@ -729,17 +736,20 @@ namespace currency
BOOST_SERIALIZE(meta_info)
BOOST_SERIALIZE(owner)
BOOST_SERIALIZE(hidden_supply)
BOOST_END_VERSION_UNDER(1)
BOOST_SERIALIZE(owner_eth_pub_key)
END_BOOST_SERIALIZATION()
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for given asset, can't be changed after deployment") DOC_EXMP(1000000000000000000) DOC_END
KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for given asset (ignored for REGISTER operation)") DOC_EXMP(500000000000000000) DOC_END
KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point") DOC_EXMP(12) DOC_END
KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with asset") DOC_EXMP("ZUSD") DOC_END
KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset") DOC_EXMP("Zano wrapped USD") DOC_END
KV_SERIALIZE(meta_info) DOC_DSCR("Any other information assetiaded with asset in a free form") DOC_EXMP("Stable and private") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, could be changed by transferring asset ownership") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE(hidden_supply) DOC_DSCR("This one reserved for future use, will be documented later") DOC_END
KV_SERIALIZE(total_max_supply) DOC_DSCR("Maximum possible supply for a given asset, cannot be changed after deployment.") DOC_EXMP(1000000000000000000) DOC_END
KV_SERIALIZE(current_supply) DOC_DSCR("Currently emitted supply for the given asset (ignored for REGISTER operation).") DOC_EXMP(500000000000000000) DOC_END
KV_SERIALIZE(decimal_point) DOC_DSCR("Decimal point.") DOC_EXMP(12) DOC_END
KV_SERIALIZE(ticker) DOC_DSCR("Ticker associated with the asset.") DOC_EXMP("ZABC") DOC_END
KV_SERIALIZE(full_name) DOC_DSCR("Full name of the asset.") DOC_EXMP("Zano wrapped ABC") DOC_END
KV_SERIALIZE(meta_info) DOC_DSCR("Any other information associated with the asset in free form.") DOC_EXMP("Stable and private") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(owner) DOC_DSCR("Owner's key, used only for EMIT and UPDATE validation, can be changed by transferring asset ownership.") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE(hidden_supply) DOC_DSCR("This field is reserved for future use and will be documented later.") DOC_END
KV_SERIALIZE(owner_eth_pub_key) DOC_DSCR("[Optional] Owner's key in the case when ETH signature is used.") DOC_END
END_KV_SERIALIZE_MAP()
};
@ -840,6 +850,26 @@ namespace currency
};
struct asset_operation_ownership_proof_eth
{
crypto::eth_signature eth_sig; // 64 bytes
uint8_t version = 0;
BEGIN_VERSIONED_SERIALIZE(0, version)
FIELD(eth_sig)
END_SERIALIZE()
BEGIN_BOOST_SERIALIZATION()
BOOST_SERIALIZE(eth_sig)
BOOST_SERIALIZE(version)
END_BOOST_SERIALIZATION()
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_POD_AS_HEX_STRING(eth_sig) DOC_DSCR("HEX-encoded ETH signature (64 bytes)") DOC_EXMP("674bb56a5b4fa562e679ccacc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6add697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE(version) DOC_DSCR("Structure version") DOC_EXMP(0) DOC_END
END_KV_SERIALIZE_MAP()
};
struct extra_padding
{
std::vector<uint8_t> buff; //stub
@ -943,7 +973,7 @@ namespace currency
typedef boost::variant<NLSAG_sig, void_sig, ZC_sig, zarcanum_sig> signature_v;
typedef boost::variant<zc_asset_surjection_proof, zc_outs_range_proof, zc_balance_proof, asset_operation_proof, asset_operation_ownership_proof> proof_v;
typedef boost::variant<zc_asset_surjection_proof, zc_outs_range_proof, zc_balance_proof, asset_operation_proof, asset_operation_ownership_proof, asset_operation_ownership_proof_eth> proof_v;
//include backward compatibility defintions
@ -1202,6 +1232,10 @@ SET_VARIANT_TAGS(currency::zc_balance_proof, 48, "zc_balance_proof");
SET_VARIANT_TAGS(currency::asset_descriptor_operation, 49, "asset_descriptor_base");
SET_VARIANT_TAGS(currency::asset_operation_proof, 50, "asset_operation_proof");
SET_VARIANT_TAGS(currency::asset_operation_ownership_proof, 51, "asset_operation_ownership_proof");
SET_VARIANT_TAGS(currency::asset_operation_ownership_proof_eth, 52, "asset_operation_ownership_proof_eth");
SET_VARIANT_TAGS(crypto::eth_public_key, 60, "eth_public_key");
//SET_VARIANT_TAGS(crypto::eth_signature, 61, "eth_signature");s

View file

@ -2196,6 +2196,8 @@ namespace currency
hsc.add_scalar(crypto::scalar_t(ado.descriptor.total_max_supply));
hsc.add_scalar(crypto::scalar_t(ado.descriptor.decimal_point));
hsc.add_pub_key(ado.descriptor.owner);
if (ado.descriptor.owner_eth_pub_key.has_value())
hsc.add_eth_pub_key(ado.descriptor.owner_eth_pub_key.value());
crypto::hash h = hsc.calc_hash_no_reduce();
// this hash function needs to be computationally expensive (s.a. the whitepaper)
@ -2248,7 +2250,8 @@ namespace currency
// asset_control_key = Hs(CRYPTO_HDS_ASSET_CONTROL_KEY, 8 * tx_key.sec * sender_account_keys.account_address.spend_public_key, 0)
// ado.descriptor.owner = asset_control_key * G
ado.descriptor.owner = sender_account_keys.account_address.spend_public_key;
if (!ado.descriptor.owner_eth_pub_key.has_value())
ado.descriptor.owner = sender_account_keys.account_address.spend_public_key;
CHECK_AND_ASSERT_MES(get_or_calculate_asset_id(ado, &gen_context.ao_asset_id_pt, &gen_context.ao_asset_id), false, "get_or_calculate_asset_id failed");
@ -2337,25 +2340,6 @@ namespace currency
}
if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_handle_asset_descriptor_operation_before_seal{ &ado });
ftp.need_to_generate_ado_proof = true;
/*
//seal it with owners signature
crypto::signature sig = currency::null_sig;
crypto::hash h = get_signature_hash_for_asset_operation(ado);
if (ftp.pthirdparty_sign_handler)
{
bool r = ftp.pthirdparty_sign_handler->sign(h, ftp.ado_current_asset_owner, sig);
CHECK_AND_ASSERT_MES(r, false, "asset thirparty sign failed");
}
else
{
crypto::public_key pub_k = currency::null_pkey;
crypto::secret_key_to_public_key(sender_account_keys.spend_secret_key, pub_k);
CHECK_AND_ASSERT_MES(ftp.ado_current_asset_owner == pub_k, false, "asset owner key not matched with provided private key for asset operation signing");
crypto::generate_signature(h, pub_k, account_keys.spend_secret_key, sig);
}
ado.opt_proof = sig;
*/
}
return true;
}
@ -2605,8 +2589,7 @@ namespace currency
// ASSET oprations handling
if (tx.version > TRANSACTION_VERSION_PRE_HF4)
{
asset_descriptor_operation* pado = nullptr;
pado = get_type_in_variant_container<asset_descriptor_operation>(tx.extra);
asset_descriptor_operation* pado = get_type_in_variant_container<asset_descriptor_operation>(tx.extra);
if (pado)
{
bool r = construct_tx_handle_ado(sender_account_keys, ftp, *pado, gen_context, gen_context.tx_key, shuffled_dsts);
@ -2719,7 +2702,8 @@ namespace currency
// generate proofs and signatures
// (any changes made below should only affect the signatures/proofs and should not impact the prefix hash calculation)
//
crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx);
result.tx_id = get_transaction_prefix_hash(tx);
const crypto::hash &tx_prefix_hash = result.tx_id;
// ring signatures (per-input proofs)
r = false;
@ -2775,35 +2759,30 @@ namespace currency
CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed");
tx.proofs.emplace_back(std::move(balance_proof));
// asset operation proof (if necessary)
// optional asset operation proofs: amount commitment proof (required for register, emit, public burn)
if (gen_context.ao_asset_id != currency::null_pkey)
{
// construct the asset operation proof
// TODO @#@# add support for hidden supply
// asset amount commitment g proof (TODO @#@# add support for hidden supply)
crypto::signature aop_g_sig{};
crypto::generate_signature(tx_prefix_hash, crypto::point_t(gen_context.ao_amount_blinding_mask * crypto::c_point_G).to_public_key(), gen_context.ao_amount_blinding_mask, aop_g_sig);
asset_operation_proof aop{};
aop.opt_amount_commitment_g_proof = aop_g_sig;
tx.proofs.emplace_back(std::move(aop));
}
if(ftp.need_to_generate_ado_proof)
{
asset_operation_ownership_proof aoop = AUTO_VAL_INIT(aoop);
if (ftp.pthirdparty_sign_handler)
// optional asset operation proofs: ownership proof for standard (non-eth) owner (using generic Shnorr signature with the spend secret key)
const asset_descriptor_operation* pado = get_type_in_variant_container<asset_descriptor_operation>(tx.extra);
if (pado != nullptr)
{
if ((pado->operation_type == ASSET_DESCRIPTOR_OPERATION_EMIT || pado->operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE) &&
!pado->descriptor.owner_eth_pub_key.has_value())
{
//ask third party to generate proof
r = ftp.pthirdparty_sign_handler->sign(tx_prefix_hash, ftp.ado_current_asset_owner, aoop.gss);
CHECK_AND_ASSERT_MES(r, false, "Failed to sign ado by thirdparty");
}
else
{
//generate signature by wallet account
r = crypto::generate_schnorr_sig(tx_prefix_hash, ftp.ado_current_asset_owner, sender_account_keys.spend_secret_key, aoop.gss);
asset_operation_ownership_proof aoop{};
r = crypto::generate_schnorr_sig(tx_prefix_hash, sender_account_keys.spend_secret_key, aoop.gss);
CHECK_AND_ASSERT_MES(r, false, "Failed to sign ado proof");
if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_after_asset_ownership_proof_generated{ &aoop });
tx.proofs.emplace_back(aoop);
}
if (ftp.pevents_dispatcher) ftp.pevents_dispatcher->RAISE_DEBUG_EVENT(wde_construct_tx_after_asset_ownership_proof_generated{ &aoop });
tx.proofs.emplace_back(aoop);
}
}

View file

@ -139,11 +139,13 @@ namespace currency
bool hltc_our_out_is_before_expiration;
};
struct thirdparty_sign_handler
struct asset_eth_signer_i
{
virtual bool sign(const crypto::hash& h, const crypto::public_key& owner_public_key, crypto::generic_schnorr_sig& sig);
virtual bool sign(const crypto::hash& h, const crypto::eth_public_key& asset_owner, crypto::eth_signature& sig) = 0;
};
typedef boost::variant<crypto::public_key, crypto::eth_public_key> asset_owner_key_v;
struct finalize_tx_param
{
uint64_t unlock_time;
@ -165,10 +167,6 @@ namespace currency
epee::misc_utils::events_dispatcher* pevents_dispatcher;
tx_generation_context gen_context{}; // solely for consolidated txs
//crypto::secret_key asset_control_key = currency::null_skey;
crypto::public_key ado_current_asset_owner = null_pkey;
thirdparty_sign_handler* pthirdparty_sign_handler = nullptr;
mutable bool need_to_generate_ado_proof = false;
BEGIN_SERIALIZE_OBJECT()
@ -191,14 +189,13 @@ namespace currency
{
FIELD(gen_context);
}
FIELD(ado_current_asset_owner)
FIELD(need_to_generate_ado_proof)
END_SERIALIZE()
};
struct finalized_tx
{
currency::transaction tx;
crypto::hash tx_id;
crypto::secret_key one_time_key;
finalize_tx_param ftp;
std::string htlc_origin;
@ -208,6 +205,7 @@ namespace currency
BEGIN_SERIALIZE_OBJECT()
FIELD(tx)
FIELD(tx_id)
FIELD(one_time_key)
FIELD(ftp)
FIELD(htlc_origin)
@ -546,7 +544,7 @@ namespace currency
{
assets_list.push_back(currency::asset_descriptor_with_id());
assets_list.back().asset_id = pr.first;
epee::misc_utils::cast_assign_a_to_b(assets_list.back(), static_cast<currency::asset_descriptor_base>(pr.second));
epee::misc_utils::cast_assign_a_to_b(static_cast<currency::asset_descriptor_base>(pr.second), assets_list.back());
//*static_cast<currency::asset_descriptor_base*>(&assets_list.back()) = pr.second;
}
}
@ -948,6 +946,12 @@ namespace currency
}
}
//---------------------------------------------------------------
template<typename invocable_t>
typename std::enable_if_t<std::is_invocable_v<invocable_t, std::ostream&>, std::ostream&> operator<<(std::ostream& o, invocable_t callee)
{
callee(o);
return o;
}
//---------------------------------------------------------------
std::ostream& operator <<(std::ostream& o, const ref_by_id& r);
std::ostream& operator <<(std::ostream& o, const std::type_info& ti);

@ -1 +1 @@
Subproject commit 6d212d4eefaf6d13c72799cb89be2c80b1813d38
Subproject commit 748e8e96d8f2653e6e698a11f67c172c1f84c2b2

View file

@ -75,7 +75,8 @@ namespace currency
return true;
}
#define check_core_ready() check_core_ready_(LOCAL_FUNCTION_DEF__)
#define CHECK_CORE_READY() if(!check_core_ready()){res.status = API_RETURN_CODE_BUSY;return true;}
#define CHECK_CORE_READY() if (!check_core_ready()) {res.status = API_RETURN_CODE_BUSY; return true; }
#define CHECK_CORE_READY_WE() if (!check_core_ready()) {error_resp.code = CORE_RPC_ERROR_CODE_CORE_BUSY; error_resp.message = "Core is busy."; return false; }
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_height(const COMMAND_RPC_GET_HEIGHT::request& req, COMMAND_RPC_GET_HEIGHT::response& res, connection_context& cntx)
{
@ -750,6 +751,74 @@ namespace currency
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
{
#define LOCAL_CHECK(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; }
#define LOCAL_CHECK_INT_ERR(cond, msg) if (!(cond)) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; error_resp.message = msg; LOG_PRINT_L1("on_decrypt_tx_details: " << error_resp.message); return false; }
LOCAL_CHECK(req.tx_id.empty() != req.tx_blob.empty(), "One of either tx_id or tx_blob must be specified.");
transaction tx{};
if (!req.tx_id.empty())
{
CHECK_CORE_READY_WE();
crypto::hash tx_id{};
LOCAL_CHECK(crypto::parse_tpod_from_hex_string(req.tx_id, tx_id), "tx_id is given, but it's invalid");
LOCAL_CHECK(m_core.get_transaction(tx_id, tx), "tx with the given tx_id could be found in the blockchain");
}
else
{
blobdata decoded_blob = string_encoding::base64_decode(req.tx_blob);
if (!t_unserializable_object_from_blob(tx, decoded_blob))
{
// unable to decode tx_blob as base64, try once again as hex-encoding
decoded_blob.clear();
string_tools::parse_hexstr_to_binbuff(req.tx_blob, decoded_blob);
LOCAL_CHECK(t_unserializable_object_from_blob(tx, decoded_blob), "tx_id is not given, and tx_blob is invalid");
}
}
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
crypto::point_t R{};
LOCAL_CHECK(tx_pub_key != null_pkey && R.from_public_key(tx_pub_key) && R.is_in_main_subgroup(), "unsigned_tx: tx public key is missing or invalid");
LOCAL_CHECK(tx_pub_key == (crypto::scalar_t(req.tx_secret_key) * crypto::c_point_G).to_public_key(), "tx_secret_key doesn't match the transaction public key");
LOCAL_CHECK(req.outputs_addresses.size() == tx.vout.size(), "outputs_addresses count (" + epee::string_tools::num_to_string_fast(req.outputs_addresses.size()) + " doesn't match tx.vout size (" + epee::string_tools::num_to_string_fast(tx.vout.size()) + ")");
for(size_t i = 0; i < req.outputs_addresses.size(); ++i)
{
if (req.outputs_addresses[i].empty())
continue; // skip this output if the given address is empty string
account_public_address addr{};
payment_id_t payment_id{};
LOCAL_CHECK(currency::get_account_address_and_payment_id_from_str(addr, payment_id, req.outputs_addresses[i]) && payment_id.empty(), "output address #" + epee::string_tools::num_to_string_fast(i) + " couldn't be parsed or it is an integrated address (which is not supported)");
tx_out_v& out_v = tx.vout[i];
LOCAL_CHECK(out_v.type() == typeid(tx_out_zarcanum), "tx output #" + epee::string_tools::num_to_string_fast(i) + " has wrong type");
const tx_out_zarcanum& zo = boost::get<tx_out_zarcanum>(out_v);
crypto::key_derivation derivation{};
LOCAL_CHECK_INT_ERR(crypto::generate_key_derivation(addr.view_public_key, req.tx_secret_key, derivation), "output #" + epee::string_tools::num_to_string_fast(i) + ": generate_key_derivation failed");
auto& decoded_out = res.decoded_outputs.emplace_back();
decoded_out.out_index = i;
decoded_out.address = req.outputs_addresses[i];
crypto::scalar_t amount_blinding_mask{}, asset_id_blinding_mask{};
LOCAL_CHECK(currency::decode_output_amount_and_asset_id(zo, derivation, i, decoded_out.amount, decoded_out.asset_id, amount_blinding_mask, asset_id_blinding_mask), "output #" + epee::string_tools::num_to_string_fast(i) + ": cannot be decoded");
}
res.tx_in_json = currency::obj_to_json_str(tx);
res.verified_tx_id = get_transaction_hash(tx);
res.status = API_RETURN_CODE_OK;
return true;
#undef LOCAL_CHECK
#undef LOCAL_CHECK_INT_ERR
}
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
{
if (!m_core.get_blockchain_storage().get_main_block_rpc_details(req.id, res.block_details))
@ -1001,7 +1070,7 @@ namespace currency
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_submitblock(const COMMAND_RPC_SUBMITBLOCK::request& req, COMMAND_RPC_SUBMITBLOCK::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
{
CHECK_CORE_READY();
CHECK_CORE_READY_WE();
if(req.size()!=1)
{
error_resp.code = CORE_RPC_ERROR_CODE_WRONG_PARAM;
@ -1044,8 +1113,7 @@ namespace currency
//------------------------------------------------------------------------------------------------------------------------------
bool core_rpc_server::on_submitblock2(const COMMAND_RPC_SUBMITBLOCK2::request& req, COMMAND_RPC_SUBMITBLOCK2::response& res, epee::json_rpc::error& error_resp, connection_context& cntx)
{
CHECK_CORE_READY();
CHECK_CORE_READY_WE();
block b = AUTO_VAL_INIT(b);
if (!parse_and_validate_block_from_blob(req.b, b))

View file

@ -1,4 +1,4 @@
// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2024 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
@ -89,6 +89,7 @@ namespace currency
bool on_get_votes(const COMMAND_RPC_GET_VOTES::request& req, COMMAND_RPC_GET_VOTES::response& res, connection_context& cntx);
bool on_get_asset_info(const COMMAND_RPC_GET_ASSET_INFO::request& req, COMMAND_RPC_GET_ASSET_INFO::response& res, connection_context& cntx);
bool on_get_assets_list(const COMMAND_RPC_GET_ASSETS_LIST::request& req, COMMAND_RPC_GET_ASSETS_LIST::response& res, connection_context& cntx);
bool on_decrypt_tx_details(const COMMAND_RPC_DECRYPT_TX_DETAILS::request& req, COMMAND_RPC_DECRYPT_TX_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx);
bool on_get_main_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx);
bool on_get_alt_block_details(const COMMAND_RPC_GET_BLOCK_DETAILS::request& req, COMMAND_RPC_GET_BLOCK_DETAILS::response& res, epee::json_rpc::error& error_resp, connection_context& cntx);
@ -153,9 +154,11 @@ namespace currency
MAP_JON_RPC ("getrandom_outs1", on_get_random_outs1, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS)
MAP_JON_RPC ("getrandom_outs3", on_get_random_outs3, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS3)
MAP_JON_RPC ("get_votes", on_get_votes, COMMAND_RPC_GET_VOTES)
//assets api
MAP_JON_RPC ("get_asset_info", on_get_asset_info, COMMAND_RPC_GET_ASSET_INFO)
MAP_JON_RPC ("get_assets_list", on_get_assets_list, COMMAND_RPC_GET_ASSETS_LIST)
MAP_JON_RPC_WE("decrypt_tx_details", on_decrypt_tx_details, COMMAND_RPC_DECRYPT_TX_DETAILS)
MAP_JON_RPC_WE("get_main_block_details", on_get_main_block_details, COMMAND_RPC_GET_BLOCK_DETAILS)
MAP_JON_RPC_WE("get_alt_block_details", on_get_alt_block_details, COMMAND_RPC_GET_BLOCK_DETAILS)

View file

@ -179,6 +179,58 @@ namespace currency
};
};
struct COMMAND_RPC_DECRYPT_TX_DETAILS
{
DOC_COMMAND("Decrypts transaction private information. Should be used only with your own local daemon for security reasons.");
struct request
{
std::string tx_id;
currency::blobdata tx_blob;
crypto::secret_key tx_secret_key;
std::vector<std::string> outputs_addresses;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_id) DOC_DSCR("[either] ID for a transaction if it is already in the blockchain. Can be ommited if tx_blob is provided.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END
KV_SERIALIZE(tx_blob) DOC_DSCR("[or] base64-encoded or hex-encoded tx blob. Can be ommited if tx_id is provided.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(tx_secret_key) DOC_DSCR("Hex-encoded transaction secret key.") DOC_EXMP("2e0b840e70dba386effd64c5d988622dea8c064040566e6bf035034cbb54a5c08") DOC_END
KV_SERIALIZE(outputs_addresses) DOC_DSCR("Address of each of tx's output. Order is important and should correspond to order of tx's outputs. Empty strings are ignored.") DOC_EXMP_AGGR("ZxDNaMeZjwCjnHuU5gUNyrP1pM3U5vckbakzzV6dEHyDYeCpW8XGLBFTshcaY8LkG9RQn7FsQx8w2JeJzJwPwuDm2NfixPAXf", "ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END
END_KV_SERIALIZE_MAP()
};
// TODO consider reusing existing structure transfer_destination -- sowle
struct decoded_output
{
uint64_t amount = 0;
std::string address;
crypto::public_key asset_id;
uint64_t out_index;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(amount) DOC_DSCR("Amount begin transferred.") DOC_EXMP(10000000000000) DOC_END
KV_SERIALIZE(address) DOC_DSCR("Destination address.") DOC_EXMP("ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) DOC_DSCR("Asset id.") DOC_EXMP("cc608f59f8080e2fbfe3c8c80eb6e6a953d47cf2d6aebd345bada3a1cab99852") DOC_END
KV_SERIALIZE(out_index) DOC_DSCR("Index of the corresponding output in the transaction.") DOC_EXMP(1) DOC_END
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string status;
std::vector<decoded_output> decoded_outputs;
std::string tx_in_json;
crypto::hash verified_tx_id;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status) DOC_DSCR("Status code of operation, OK if success") DOC_EXMP(API_RETURN_CODE_OK) DOC_END
KV_SERIALIZE(decoded_outputs) DOC_DSCR("Transaction's decoded outputs") DOC_EXMP_AUTO(1) DOC_END
KV_SERIALIZE_BLOB_AS_BASE64_STRING(tx_in_json) DOC_DSCR("Serialized transaction represented in JSON, encoded in Base64.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(verified_tx_id) DOC_DSCR("(Re)calculated transaction id. Can be used in third-party proof generation.") DOC_EXMP("a6e8da986858e6825fce7a192097e6afae4e889cabe853a9c29b964985b23da8") DOC_END
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_RPC_GET_HEIGHT
{
DOC_COMMAND("Return current blockchain height");
@ -643,12 +695,14 @@ namespace currency
struct request
{
std::string tx_as_hex;
std::string tx_as_json;
request() {}
explicit request(const transaction &);
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(tx_as_hex) DOC_DSCR("The transaction data as a hexadecimal string, ready for network broadcast.") DOC_EXMP("00018ed1535b8b4862e.....368cdc5a86") DOC_END
KV_SERIALIZE(tx_as_hex) DOC_DSCR("[either] The transaction data as a hexadecimal string, ready for network broadcast.") DOC_EXMP("00018ed1535b8b4862e.....368cdc5a86") DOC_END
KV_SERIALIZE_BLOB_AS_BASE64_STRING(tx_as_json) DOC_DSCR("[or] The transaction data as a base64-encoded json, ready for network broadcast.") DOC_EXMP("ARMBgKCUpY0dBBoAAAAAAAAAABoCAAAAA.......AAAAAAAAABoPAAAAAAAAACVA4FRLH") DOC_END
END_KV_SERIALIZE_MAP()
};

View file

@ -1,3 +1,4 @@
// Copyright (c) 2018-2024 Zano Project
// Copyright (c) 2014-2017 The The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
@ -7,7 +8,7 @@
#include <boost/serialization/optional.hpp>
// boost::optional
template <template <bool> class Archive, class T>
bool do_serialize(Archive<false> &ar, boost::optional<T> &o)
{
@ -58,3 +59,55 @@ bool do_serialize(Archive<true> &ar, boost::optional<T> &v)
return true;
}
// std::optional
template <template <bool> class Archive, class T>
bool do_serialize(Archive<false> &ar, std::optional<T> &o)
{
//reading flag
bool is_none = false;
if (!::do_serialize(ar, is_none))
{
ar.stream().setstate(std::ios::failbit);
return false;
}
if (is_none)
{
o.reset();
return true;
}
o = T();
T& rval = o.value();
//reading value
if (!::do_serialize(ar, rval))
{
ar.stream().setstate(std::ios::failbit);
return false;
}
return true;
}
template <template <bool> class Archive, class T>
bool do_serialize(Archive<true> &ar, std::optional<T> &v)
{
//writing flag
bool is_none = !v.has_value();
if (!::do_serialize(ar, is_none))
{
ar.stream().setstate(std::ios::failbit);
return false;
}
if (is_none)
{
return true;
}
if (!::do_serialize(ar, v.value()))
{
ar.stream().setstate(std::ios::failbit);
return false;
}
return true;
}

View file

@ -238,8 +238,7 @@ bool t_unserializable_object_from_blob(t_object& to, const std::string& blob)
ss << blob;
binary_archive<false> ba(ss);
bool r = ::serialization::serialize(ba, to);
CHECK_AND_ASSERT_MES(r, false, "Failed to parse block from blob");
return true;
return r;
}
//---------------------------------------------------------------
template<class t_object>

View file

@ -125,7 +125,7 @@ namespace
{
const command_line::arg_descriptor<std::string> arg_wallet_file ("wallet-file", "Use wallet <arg>", "");
const command_line::arg_descriptor<std::string> arg_generate_new_wallet ("generate-new-wallet", "Generate new wallet and save it to <arg> or <address>.wallet by default", "");
const command_line::arg_descriptor<bool> arg_derive_custom_seed("derive_custom_seed", "Derive seed phrase from custom 24-words secret(advanced option, do it on your own risk)", "");
const command_line::arg_descriptor<bool> arg_derive_custom_seed("derive_custom_seed", "Derive seed phrase from custom 24-words secret(advanced option, do it on your own risk)", "");
const command_line::arg_descriptor<std::string> arg_generate_new_auditable_wallet ("generate-new-auditable-wallet", "Generate new auditable wallet and store it to <arg>", "");
const command_line::arg_descriptor<std::string> arg_daemon_address ("daemon-address", "Use daemon instance at <host>:<port>", "");
const command_line::arg_descriptor<std::string> arg_daemon_host ("daemon-host", "Use daemon instance at host <arg> instead of localhost", "");
@ -2094,11 +2094,11 @@ bool simple_wallet::deploy_new_asset(const std::vector<std::string> &args)
td.asset_id = currency::null_pkey;
std::vector<currency::tx_destination_entry> destinations;
destinations.push_back(td);
currency::transaction result_tx = AUTO_VAL_INIT(result_tx);
currency::finalized_tx ft{};
crypto::public_key result_asset_id = currency::null_pkey;
m_wallet->deploy_new_asset(adb, destinations, result_tx, result_asset_id);
m_wallet->deploy_new_asset(adb, destinations, ft, result_asset_id);
success_msg_writer(true) << "New asset successfully deployed with tx " << get_transaction_hash(result_tx) << " (unconfirmed) : " << ENDL
success_msg_writer(true) << "New asset successfully deployed with tx " << ft.tx_id << " (unconfirmed) : " << ENDL
<< "Asset ID: " << result_asset_id << ENDL
<< "Title: " << adb.full_name << ENDL
<< "Ticker: " << adb.ticker << ENDL
@ -3200,7 +3200,7 @@ int main(int argc, char* argv[])
command_line::add_arg(desc_params, command_line::arg_generate_rpc_autodoc);
command_line::add_arg(desc_params, arg_seed_doctor);
command_line::add_arg(desc_params, arg_derive_custom_seed);
tools::wallet_rpc_server::init_options(desc_params);
@ -3278,7 +3278,6 @@ int main(int argc, char* argv[])
bool offline_mode = command_line::get_arg(vm, arg_offline_mode);
if (command_line::has_arg(vm, arg_seed_doctor))
{
return seed_doctor();

View file

@ -194,7 +194,6 @@ namespace currency
std::string m_restore_wallet;
std::string m_voting_config_file;
bool m_no_password_confirmations = false;
crypto::hash m_password_hash;
uint64_t m_password_salt;

View file

@ -9,7 +9,6 @@
#include <boost/serialization/deque.hpp>
#include <boost/serialization/singleton.hpp>
#include <boost/serialization/extended_type_info.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/optional.hpp>
#include <atomic>

View file

@ -129,7 +129,7 @@ namespace tools
//----------------------------------------------------------------------------------------------------
std::string wallet2::transfer_flags_to_str(uint32_t flags)
{
std::string result(5, ' ');
std::string result(7, ' ');
if (flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT)
result[0] = 's';
if (flags & WALLET_TRANSFER_DETAIL_FLAG_BLOCKED)
@ -140,6 +140,10 @@ std::string wallet2::transfer_flags_to_str(uint32_t flags)
result[3] = 'm';
if (flags & WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION)
result[4] = 'c';
if (flags & WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM)
result[5] = 'h';
if (flags & WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION)
result[6] = 'a';
return result;
}
//----------------------------------------------------------------------------------------------------
@ -390,6 +394,10 @@ const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out
//----------------------------------------------------------------------------------------------------
void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_operation& ado, process_transaction_context& ptc)
{
auto print_ado_owner = [ado](std::ostream& o){
ado.descriptor.owner_eth_pub_key.has_value() ? o << ado.descriptor.owner_eth_pub_key.get() << " (ETH)" : o << ado.descriptor.owner;
};
do
{
crypto::public_key asset_id{};
@ -398,17 +406,22 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op
if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER)
{
if (ado.descriptor.owner != m_account.get_public_address().spend_public_key)
// Add an asset to ownership list if either:
// 1) we're the owner of the asset;
// or
// 2) we spent native coins in the tx (i.e. we sent it) AND it registers an asset with third-party ownership.
if (ado.descriptor.owner != m_account.get_public_address().spend_public_key &&
(!ado.descriptor.owner_eth_pub_key.has_value() || !ptc.spent_own_native_inputs))
break;
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(m_own_asset_descriptors.count(asset_id) == 0, "asset with asset_id " << asset_id << " has already been registered in the wallet as own asset");
wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id];
epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor);
//*static_cast<asset_descriptor_base*>(&asset_context) = ado.descriptor;
epee::misc_utils::cast_assign_a_to_b(ado.descriptor, asset_context);
std::stringstream ss;
ss << "New Asset Registered:"
<< ENDL << "asset id: " << asset_id
<< ENDL << "Owner: " << print_ado_owner
<< ENDL << "Name: " << asset_context.full_name
<< ENDL << "Ticker: " << asset_context.ticker
<< ENDL << "Total Max Supply: " << print_asset_money(asset_context.total_max_supply, asset_context.decimal_point)
@ -428,7 +441,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op
break;
//asset had been updated
add_rollback_event(ptc.height, asset_update_event{ it->first, it->second });
epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor);
epee::misc_utils::cast_assign_a_to_b(ado.descriptor, it->second);
}
else if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_UPDATE )
@ -441,7 +454,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op
// ownership of the asset acquired
wallet_own_asset_context& asset_context = m_own_asset_descriptors[asset_id];
epee::misc_utils::cast_assign_a_to_b(asset_context, ado.descriptor);
epee::misc_utils::cast_assign_a_to_b(ado.descriptor, asset_context);
std::stringstream ss;
ss << "Asset ownership acquired:"
@ -466,8 +479,8 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op
}
else
{
//update event for asset that we control, check if ownership is still ours
if (ado.descriptor.owner != m_account.get_public_address().spend_public_key && !it->second.thirdparty_custody)
// check our ownership status: we lost it if the asset has new non-null owner (null means a third-party ownership, and in such a case we retain it in the own list whatever happens)
if (ado.descriptor.owner != null_pkey && ado.descriptor.owner != m_account.get_public_address().spend_public_key)
{
//ownership of the asset had been transfered
add_rollback_event(ptc.height, asset_unown_event{ it->first, it->second });
@ -476,7 +489,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op
std::stringstream ss;
ss << "Asset ownership lost:"
<< ENDL << "asset id: " << asset_id
<< ENDL << "New owner: " << ado.descriptor.owner
<< ENDL << "New owner: " << print_ado_owner
<< ENDL << "Name: " << ado.descriptor.full_name
<< ENDL << "Ticker: " << ado.descriptor.ticker
<< ENDL << "Total Max Supply: " << print_asset_money(ado.descriptor.total_max_supply, ado.descriptor.decimal_point)
@ -492,8 +505,7 @@ void wallet2::process_ado_in_new_transaction(const currency::asset_descriptor_op
{
//just an update of the asset
add_rollback_event(ptc.height, asset_update_event{ it->first, it->second });
epee::misc_utils::cast_assign_a_to_b(it->second, ado.descriptor);
epee::misc_utils::cast_assign_a_to_b(ado.descriptor, it->second);
}
}
}
@ -1151,7 +1163,7 @@ void wallet2::accept_proposal(const crypto::hash& contract_id, uint64_t b_accept
throw;
}
print_tx_sent_message(tx, "(contract <" + epee::string_tools::pod_to_hex(contract_id) + ">)", construct_param.fee);
print_tx_sent_message(tx, "contract <" + epee::string_tools::pod_to_hex(contract_id) + ">", true, construct_param.fee);
if (p_acceptance_tx != nullptr)
*p_acceptance_tx = tx;
@ -1290,7 +1302,7 @@ void wallet2::request_cancel_contract(const crypto::hash& contract_id, uint64_t
throw;
}
print_tx_sent_message(tx, "(transport for cancel proposal)", fee);
print_tx_sent_message(tx, "transport for cancel proposal", true, fee);
if (p_cancellation_proposal_tx != nullptr)
*p_cancellation_proposal_tx = tx;
@ -4004,7 +4016,7 @@ bool wallet2::generate_utxo_defragmentation_transaction_if_needed(currency::tran
//----------------------------------------------------------------------------------------------------
std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool include_unspent /*= true*/, bool show_only_unknown /*= false*/, const std::string& filter_asset_ticker /*= std::string{}*/) const
{
static const char* header = " index amount ticker g_index flags block tx out# asset id";
static const char* header = " index amount ticker g_index flags block tx out# asset id";
std::stringstream ss;
ss << header << ENDL;
size_t count = 0;
@ -4036,7 +4048,7 @@ std::string wallet2::get_transfers_str(bool include_spent /*= true*/, bool inclu
std::setw(6) << std::left << (native_coin ? std::string(" ") : adb.ticker) << " " << std::right <<
std::setw(7) << td.m_global_output_index << " " <<
std::setw(2) << std::setfill('0') << td.m_flags << std::setfill(' ') << ":" <<
std::setw(5) << transfer_flags_to_str(td.m_flags) << " " <<
std::setw(7) << transfer_flags_to_str(td.m_flags) << " " <<
std::setw(7) << td.m_ptx_wallet_info->m_block_height << " " <<
get_transaction_hash(td.m_ptx_wallet_info->m_tx) << " " <<
std::setw(4) << td.m_internal_output_index << " ";
@ -4300,6 +4312,39 @@ bool wallet2::get_utxo_distribution(std::map<uint64_t, uint64_t>& distribution)
return false;
}
//----------------------------------------------------------------------------------------------------
void wallet2::submit_externally_signed_asset_tx(const finalized_tx& ft, const crypto::eth_signature& eth_sig, bool unlock_transfers_on_fail, currency::transaction& result_tx, bool& transfers_unlocked)
{
transaction tx = ft.tx;
currency::asset_operation_ownership_proof_eth aoop_eth{};
aoop_eth.eth_sig = eth_sig;
tx.proofs.push_back(std::move(aoop_eth));
// foolproof
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(ft.ftp.spend_pub_key == m_account.get_keys().account_address.spend_public_key, "The given tx was created in a different wallet, keys missmatch, tx hash: " << ft.tx_id);
try
{
send_transaction_to_network(tx);
}
catch (...)
{
// clear transfers flags if smth went wrong and it was requested
if (unlock_transfers_on_fail)
{
uint32_t flag = WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION;
clear_transfers_from_flag(ft.ftp.selected_transfers, flag, "broadcasting tx " + epee::string_tools::pod_to_hex(ft.tx_id) + " was unsuccessful");
transfers_unlocked = true;
}
throw;
}
m_tx_keys.insert(std::make_pair(ft.tx_id, ft.one_time_key));
add_sent_tx_detailed_info(tx, ft.ftp.attachments, ft.ftp.prepared_destinations, ft.ftp.selected_transfers);
print_tx_sent_message(tx, "from submit_externally_signed_asset_tx", true, get_tx_fee(tx));
}
//----------------------------------------------------------------------------------------------------
void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx)
{
// decrypt sources
@ -4390,7 +4435,7 @@ void wallet2::submit_transfer(const std::string& signed_tx_blob, currency::trans
}
// TODO: print inputs' key images
print_tx_sent_message(tx, "(from submit_transfer)");
print_tx_sent_message(tx, "from submit_transfer", true);
}
//----------------------------------------------------------------------------------------------------
void wallet2::submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx)
@ -5360,125 +5405,159 @@ void wallet2::request_alias_registration(currency::extra_alias_entry& ai, curren
transfer(destinations, 0, 0, fee, extra, attachments, get_current_split_strategy(), tx_dust_policy(DEFAULT_DUST_THRESHOLD), res_tx, CURRENCY_TO_KEY_OUT_RELAXED, false);
}
//----------------------------------------------------------------------------------------------------
void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id)
void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector<currency::tx_destination_entry>& destinations, currency::finalized_tx& ft, crypto::public_key& new_asset_id)
{
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << asset_info.decimal_point);
WLT_THROW_IF_FALSE_WALLET_CMN_ERR_EX(asset_info.decimal_point <= 18, "too big decimal point: " << (int)asset_info.decimal_point);
asset_descriptor_operation asset_reg_info = AUTO_VAL_INIT(asset_reg_info);
asset_descriptor_operation asset_reg_info{};
asset_reg_info.descriptor = asset_info;
asset_reg_info.operation_type = ASSET_DESCRIPTOR_OPERATION_REGISTER;
construct_tx_param ctp = get_default_construct_tx_param();
ctp.dsts = destinations;
ctp.extra.push_back(asset_reg_info);
ctp.need_at_least_1_zc = true;
ctp.tx_meaning_for_logs = "asset registration";
finalized_tx ft = AUTO_VAL_INIT(ft);
this->transfer(ctp, ft, true, nullptr);
result_tx = ft.tx;
//get generated asset id
currency::asset_descriptor_operation ado = AUTO_VAL_INIT(ado);
bool r = get_type_in_variant_container(result_tx.extra, ado);
CHECK_AND_ASSERT_THROW_MES(r, "Failed find asset info in tx");
currency::asset_descriptor_operation ado{};
bool r = get_type_in_variant_container(ft.tx.extra, ado);
CHECK_AND_ASSERT_THROW_MES(r, "asset_descriptor_operation cannot be found in tx extra as expected");
CHECK_AND_ASSERT_THROW_MES(get_or_calculate_asset_id(ado, nullptr, &new_asset_id), "get_or_calculate_asset_id failed");
m_custom_assets[new_asset_id] = ado.descriptor;
}
//----------------------------------------------------------------------------------------------------
void wallet2::emit_asset(const crypto::public_key asset_id, std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx)
void wallet2::deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id)
{
finalized_tx ft{};
deploy_new_asset(asset_info, destinations, ft, new_asset_id);
result_tx = ft.tx;
}
//----------------------------------------------------------------------------------------------------
void wallet2::emit_asset(const crypto::public_key& asset_id, const std::vector<currency::tx_destination_entry>& destinations, currency::finalized_tx& ft)
{
auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id);
CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list");
COMMAND_RPC_GET_ASSET_INFO::request req;
req.asset_id = asset_id;
COMMAND_RPC_GET_ASSET_INFO::response rsp;
bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO");
asset_descriptor_operation asset_emmit_info = AUTO_VAL_INIT(asset_emmit_info);
asset_emmit_info.descriptor = rsp.asset_descriptor;
currency::asset_descriptor_base last_adb{};
bool r = daemon_get_asset_info(asset_id, last_adb);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon");
asset_descriptor_operation asset_emmit_info{};
asset_emmit_info.descriptor = last_adb;
asset_emmit_info.operation_type = ASSET_DESCRIPTOR_OPERATION_EMIT;
asset_emmit_info.opt_asset_id = asset_id;
construct_tx_param ctp = get_default_construct_tx_param();
ctp.dsts = destinations;
ctp.extra.push_back(asset_emmit_info);
ctp.need_at_least_1_zc = true;
ctp.ado_current_asset_owner = rsp.asset_descriptor.owner;
//ctp.asset_deploy_control_key = own_asset_entry_it->second.control_key;
ctp.tx_meaning_for_logs = "asset emission";
for(auto& dst : ctp.dsts)
bool send_to_network = true;
if (last_adb.owner_eth_pub_key.has_value())
{
dst.asset_id = null_pkey; // emit operation requires null_pkey for emitting asset outputs, fix it ad-hoc here
send_to_network = false;
ctp.additional_transfer_flags_to_mark = WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION;
ctp.tx_meaning_for_logs = "asset eth emission";
}
finalized_tx ft = AUTO_VAL_INIT(ft);
this->transfer(ctp, ft, true, nullptr);
this->transfer(ctp, ft, send_to_network, nullptr);
}
//----------------------------------------------------------------------------------------------------
void wallet2::emit_asset(const crypto::public_key& asset_id, std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx)
{
finalized_tx ft{};
emit_asset(asset_id, destinations, ft);
result_tx = ft.tx;
}
//----------------------------------------------------------------------------------------------------
void wallet2::update_asset(const crypto::public_key asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx)
void wallet2::update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base& new_descriptor, currency::finalized_tx& ft)
{
auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id);
CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list");
currency::asset_descriptor_base last_adb{};
bool r = daemon_get_asset_info(asset_id, last_adb);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon");
asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info);
asset_descriptor_operation asset_update_info{};
asset_update_info.descriptor = new_descriptor;
asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE;
asset_update_info.opt_asset_id = asset_id;
construct_tx_param ctp = get_default_construct_tx_param();
ctp.extra.push_back(asset_update_info);
ctp.need_at_least_1_zc = true;
currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb);
bool r = this->daemon_get_asset_info(asset_id, adb);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon");
ctp.ado_current_asset_owner = adb.owner;
ctp.tx_meaning_for_logs = "asset update";
finalized_tx ft = AUTO_VAL_INIT(ft);
this->transfer(ctp, ft, true, nullptr);
bool send_to_network = true;
if (last_adb.owner_eth_pub_key.has_value())
{
send_to_network = false;
ctp.additional_transfer_flags_to_mark = WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION;
ctp.tx_meaning_for_logs = "asset eth update";
}
this->transfer(ctp, ft, send_to_network, nullptr);
}
//----------------------------------------------------------------------------------------------------
void wallet2::update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx)
{
currency::finalized_tx ft{};
update_asset(asset_id, new_descriptor, ft);
result_tx = ft.tx;
}
//----------------------------------------------------------------------------------------------------
void wallet2::transfer_asset_ownership(const crypto::public_key asset_id, const crypto::public_key& new_owner, currency::transaction& result_tx)
void wallet2::transfer_asset_ownership(const crypto::public_key& asset_id, const currency::asset_owner_pub_key_v& new_owner_v, currency::finalized_tx& ft)
{
auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id);
CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list");
currency::asset_descriptor_base adb = AUTO_VAL_INIT(adb);
bool r = this->daemon_get_asset_info(asset_id, adb);
currency::asset_descriptor_base last_adb{};
bool r = this->daemon_get_asset_info(asset_id, last_adb);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon");
asset_descriptor_operation asset_update_info = AUTO_VAL_INIT(asset_update_info);
asset_update_info.descriptor = adb;
asset_descriptor_operation asset_update_info{};
asset_update_info.descriptor = last_adb;
asset_update_info.operation_type = ASSET_DESCRIPTOR_OPERATION_UPDATE;
asset_update_info.opt_asset_id = asset_id;
asset_update_info.descriptor.owner = new_owner;
construct_tx_param ctp = get_default_construct_tx_param();
ctp.ado_current_asset_owner = adb.owner;
ctp.extra.push_back(asset_update_info);
finalized_tx ft = AUTO_VAL_INIT(ft);
this->transfer(ctp, ft, true, nullptr);
if (new_owner_v.type() == typeid(crypto::public_key))
asset_update_info.descriptor.owner = boost::get<crypto::public_key>(new_owner_v);
else
asset_update_info.descriptor.owner_eth_pub_key = boost::get<crypto::eth_public_key>(new_owner_v);
construct_tx_param ctp = get_default_construct_tx_param();
ctp.extra.push_back(asset_update_info);
ctp.tx_meaning_for_logs = "transfer asset ownership";
bool send_to_network = true;
if (last_adb.owner_eth_pub_key.has_value())
{
send_to_network = false;
ctp.additional_transfer_flags_to_mark = WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION;
ctp.tx_meaning_for_logs = "transfer asset eth ownership";
}
this->transfer(ctp, ft, send_to_network, nullptr);
}
//----------------------------------------------------------------------------------------------------
void wallet2::transfer_asset_ownership(const crypto::public_key& asset_id, const currency::asset_owner_pub_key_v& new_owner_v, currency::transaction& result_tx)
{
finalized_tx ft{};
transfer_asset_ownership(asset_id, new_owner_v, ft);
result_tx = ft.tx;
}
//----------------------------------------------------------------------------------------------------
void wallet2::burn_asset(const crypto::public_key asset_id, uint64_t amount_to_burn, currency::transaction& result_tx)
void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft)
{
//auto own_asset_entry_it = m_own_asset_descriptors.find(asset_id);
//CHECK_AND_ASSERT_THROW_MES(own_asset_entry_it != m_own_asset_descriptors.end(), "Failed find asset_id " << asset_id << " in own assets list");
COMMAND_RPC_GET_ASSET_INFO::request req;
req.asset_id = asset_id;
COMMAND_RPC_GET_ASSET_INFO::response rsp;
bool r = m_core_proxy->call_COMMAND_RPC_GET_ASSET_INFO(req, rsp);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to call_COMMAND_RPC_GET_ASSET_INFO");
currency::asset_descriptor_base last_adb{};
bool r = this->daemon_get_asset_info(asset_id, last_adb);
CHECK_AND_ASSERT_THROW_MES(r, "Failed to get asset info from daemon");
asset_descriptor_operation asset_burn_info{};
asset_burn_info.descriptor = last_adb;
CHECK_AND_ASSERT_THROW_MES(last_adb.current_supply >= amount_to_burn, "amount_to_burn is incorrect: " << print_money_brief(amount_to_burn, last_adb.decimal_point) << ", current_supply: " << print_money_brief(last_adb.current_supply, last_adb.decimal_point));
asset_descriptor_operation asset_burn_info = AUTO_VAL_INIT(asset_burn_info);
asset_burn_info.descriptor = rsp.asset_descriptor;
CHECK_AND_ASSERT_THROW_MES(asset_burn_info.descriptor.current_supply >= amount_to_burn, "Wrong amount to burn (current_supply" << asset_burn_info.descriptor.current_supply << " is less then " << amount_to_burn << ")");
currency::tx_destination_entry dst_to_burn = AUTO_VAL_INIT(dst_to_burn);
currency::tx_destination_entry dst_to_burn{};
dst_to_burn.amount = amount_to_burn;
dst_to_burn.asset_id = asset_id;
@ -5487,11 +5566,16 @@ void wallet2::burn_asset(const crypto::public_key asset_id, uint64_t amount_to_b
construct_tx_param ctp = get_default_construct_tx_param();
ctp.extra.push_back(asset_burn_info);
ctp.need_at_least_1_zc = true;
ctp.ado_current_asset_owner = rsp.asset_descriptor.owner;
ctp.dsts.push_back(dst_to_burn);
ctp.tx_meaning_for_logs = "asset burn";
finalized_tx ft = AUTO_VAL_INIT(ft);
this->transfer(ctp, ft, true, nullptr);
}
//----------------------------------------------------------------------------------------------------
void wallet2::burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::transaction& result_tx)
{
finalized_tx ft{};
burn_asset(asset_id, amount_to_burn, ft);
result_tx = ft.tx;
}
//----------------------------------------------------------------------------------------------------
@ -5961,7 +6045,7 @@ void wallet2::send_escrow_proposal(const bc_services::contract_private_details&
mark_transfers_as_spent(ftp.selected_transfers, std::string("escrow proposal sent, tx <") + epee::string_tools::pod_to_hex(get_transaction_hash(tx)) + ">, contract: " + epee::string_tools::pod_to_hex(ms_id));
add_sent_tx_detailed_info(tx, ftp.attachments, ftp.prepared_destinations, ftp.selected_transfers);
print_tx_sent_message(tx, "(from multisig)", fee);
print_tx_sent_message(tx, "from multisig", true, fee);
}
//----------------------------------------------------------------------------------------------------
void wallet2::create_htlc_proposal(uint64_t amount, const currency::account_public_address& addr, uint64_t lock_blocks_count, currency::transaction &tx, const crypto::hash& htlc_hash, std::string &origin)
@ -7418,20 +7502,20 @@ void wallet2::set_genesis(const crypto::hash& genesis_hash)
m_chain.set_genesis(genesis_hash);
}
//----------------------------------------------------------------------------------------------------
void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee /* = UINT64_MAX */)
void wallet2::print_tx_sent_message(const currency::transaction& tx, const std::string& description, bool broadcasted, uint64_t fee /* = UINT64_MAX */)
{
//uint64_t balance_unlocked = 0;
//uint64_t balance_total = balance(balance_unlocked);
std::stringstream ss;
if (fee != UINT64_MAX)
ss << "Commission: " << std::setw(21) << std::right << print_money(fee) << ENDL;
ss << "Fee: " << std::setw(21) << std::right << print_money_brief(fee) << ENDL;
WLT_LOG_CYAN("Transaction " << get_transaction_hash(tx) << " was successfully sent " << description << ENDL
WLT_LOG_CYAN("Transaction " << get_transaction_hash(tx) << " was successfully " << (broadcasted ? "sent" : "created") << " (" << description << ")" << ENDL
<< ss.str()
// << "Balance: " << std::setw(21) << print_money(balance_total) << ENDL
// << "Unlocked: " << std::setw(21) << print_money(balance_unlocked) << ENDL
<< "Please, wait for confirmation for your balance to be unlocked.",
<< (broadcasted ? "Please wait for the transaction to be confirmed before your balance is unlocked." : ""),
LOG_LEVEL_0);
}
//----------------------------------------------------------------------------------------------------
@ -7602,8 +7686,8 @@ bool wallet2::prepare_transaction(construct_tx_param& ctp, currency::finalize_tx
{
WLT_THROW_IF_FALSE_WITH_CODE(false, "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET", "WALLET_RPC_ERROR_CODE_WRONG_MIXINS_FOR_AUDITABLE_WALLET");
}
ftp.ado_current_asset_owner = ctp.ado_current_asset_owner;
ftp.pthirdparty_sign_handler = ctp.pthirdparty_sign_handler;
//ftp.asset_owner = ctp.asset_owner;
//ftp.p_eth_signer = ctp.p_eth_signer;
//
// TODO @#@# need to do refactoring over this part to support hidden amounts and asset_id
//
@ -7736,7 +7820,7 @@ void wallet2::finalize_transaction(currency::finalize_tx_param& ftp, currency::f
THROW_IF_FALSE_WALLET_EX_MES(tx_blob_size < CURRENCY_MAX_TRANSACTION_BLOB_SIZE, error::tx_too_big, "Transaction size: " << tx_blob_size << " bytes, transaction size limit: " << CURRENCY_MAX_TRANSACTION_BLOB_SIZE << " bytes.");
if (store_tx_secret_key)
m_tx_keys.insert(std::make_pair(get_transaction_hash(result.tx), result.one_time_key));
m_tx_keys.insert(std::make_pair(result.tx_id, result.one_time_key));
//TIME_MEASURE_START(send_transaction_to_network_time);
if (broadcast_tx)
@ -7914,8 +7998,11 @@ void wallet2::transfer(construct_tx_param& ctp,
return;
}
std::string tx_description = ctp.tx_meaning_for_logs.empty() ? std::string("transfer") : ctp.tx_meaning_for_logs;
uint32_t transfers_flags = ctp.additional_transfer_flags_to_mark | WALLET_TRANSFER_DETAIL_FLAG_SPENT;
TIME_MEASURE_START(mark_transfers_as_spent_time);
mark_transfers_as_spent(ftp.selected_transfers, std::string("money transfer, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx)));
mark_transfers_with_flag(ftp.selected_transfers, transfers_flags, std::string("preparing for ") + tx_description);
TIME_MEASURE_FINISH(mark_transfers_as_spent_time);
TIME_MEASURE_START(finalize_transaction_time);
@ -7925,7 +8012,7 @@ void wallet2::transfer(construct_tx_param& ctp,
}
catch (...)
{
clear_transfers_from_flag(ftp.selected_transfers, WALLET_TRANSFER_DETAIL_FLAG_SPENT, std::string("exception on money transfer, tx: ") + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx)));
clear_transfers_from_flag(ftp.selected_transfers, transfers_flags, std::string("exception during") + tx_description + ", tx (maybe incorrect if tx is incomplete): " + epee::string_tools::pod_to_hex(get_transaction_hash(result.tx)));
throw;
}
TIME_MEASURE_FINISH(finalize_transaction_time);
@ -7938,7 +8025,7 @@ void wallet2::transfer(construct_tx_param& ctp,
<< ", mark_transfers_as_spent_time: " << print_fixed_decimal_point(mark_transfers_as_spent_time, 3)
, LOG_LEVEL_0);
print_tx_sent_message(result.tx, std::string() + "(transfer)", ctp.fee);
print_tx_sent_message(result.tx, tx_description, send_to_network, ctp.fee);
}
//----------------------------------------------------------------------------------------------------
@ -8226,7 +8313,7 @@ void wallet2::sweep_below(size_t fake_outs_count, const currency::account_public
{
crypto::secret_key sk{};
finalize_transaction(ftp, *p_tx, sk, true);
print_tx_sent_message(*p_tx, "(sweep_below)", get_tx_fee(*p_tx));
print_tx_sent_message(*p_tx, "sweep_below", true, get_tx_fee(*p_tx));
}
catch (...)
{

View file

@ -420,10 +420,16 @@ namespace tools
bool check_available_sources(std::list<uint64_t>& amounts);
void deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx, crypto::public_key& new_asset_id);
void emit_asset(const crypto::public_key asset_id, std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx);
void update_asset(const crypto::public_key asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx);
void burn_asset(const crypto::public_key asset_id, uint64_t amount_to_burn, currency::transaction& result_tx);
void transfer_asset_ownership(const crypto::public_key asset_id, const crypto::public_key& new_owner, currency::transaction& result_tx);
void emit_asset(const crypto::public_key& asset_id, std::vector<currency::tx_destination_entry>& destinations, currency::transaction& result_tx);
void update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base new_descriptor, currency::transaction& result_tx);
void burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::transaction& result_tx);
void transfer_asset_ownership(const crypto::public_key& asset_id, const currency::asset_owner_pub_key_v& new_owner_v, currency::transaction& result_tx);
void deploy_new_asset(const currency::asset_descriptor_base& asset_info, const std::vector<currency::tx_destination_entry>& destinations, currency::finalized_tx& ft, crypto::public_key& new_asset_id);
void emit_asset(const crypto::public_key& asset_id, const std::vector<currency::tx_destination_entry>& destinations, currency::finalized_tx& ft);
void update_asset(const crypto::public_key& asset_id, const currency::asset_descriptor_base& new_descriptor, currency::finalized_tx& ft);
void burn_asset(const crypto::public_key& asset_id, uint64_t amount_to_burn, currency::finalized_tx& ft);
void transfer_asset_ownership(const crypto::public_key& asset_id, const currency::asset_owner_pub_key_v& new_owner_v, currency::finalized_tx& ft);
bool daemon_get_asset_info(const crypto::public_key& asset_id, currency::asset_descriptor_base& adb);
bool set_core_proxy(const std::shared_ptr<i_core_proxy>& proxy);
@ -589,6 +595,7 @@ namespace tools
void sign_transfer_files(const std::string& tx_sources_file, const std::string& signed_tx_file, currency::transaction& tx);
void submit_transfer(const std::string& signed_tx_blob, currency::transaction& tx);
void submit_transfer_files(const std::string& signed_tx_file, currency::transaction& tx);
void submit_externally_signed_asset_tx(const currency::finalized_tx& ft, const crypto::eth_signature& eth_sig, bool unlock_transfers_on_fail, currency::transaction& result_tx, bool& transfers_unlocked);
void sweep_below(size_t fake_outs_count, const currency::account_public_address& destination_addr, uint64_t threshold_amount, const currency::payment_id_t& payment_id,
uint64_t fee, size_t& outs_total, uint64_t& amount_total, size_t& outs_swept, uint64_t& amount_swept, currency::transaction* p_result_tx = nullptr, std::string* p_filename_or_unsigned_tx_blob_str = nullptr);
@ -836,7 +843,7 @@ private:
uint64_t get_tx_expiration_median() const;
void print_tx_sent_message(const currency::transaction& tx, const std::string& description, uint64_t fee = UINT64_MAX);
void print_tx_sent_message(const currency::transaction& tx, const std::string& description, bool broadcasted, uint64_t fee = UINT64_MAX);
// Validates escrow template tx in assumption it's related to wallet's account (wallet's account is either A or B party in escrow process)
bool validate_escrow_proposal(const wallet_public::wallet_transfer_info& wti, const bc_services::proposal_body& prop,

View file

@ -11,7 +11,6 @@
#include <boost/serialization/deque.hpp>
#include <boost/serialization/singleton.hpp>
#include <boost/serialization/extended_type_info.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/optional.hpp>
#include <atomic>
@ -52,6 +51,7 @@
#define WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER uint32_t(1 << 3)
#define WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION uint32_t(1 << 4) // transfer is reserved for cold-signing (unsigned tx was created and passed for signing)
#define WALLET_TRANSFER_DETAIL_FLAG_HTLC_REDEEM uint32_t(1 << 5) // for htlc keeps info if this htlc belong as redeem or as refund
#define WALLET_TRANSFER_DETAIL_FLAG_ASSET_OP_RESERVATION uint32_t(1 << 6) // transfer is reserved for an ongoing asset operation with external signing
@ -224,9 +224,10 @@ namespace tools
bool shuffle = false;
bool create_utxo_defragmentation_tx = false;
bool need_at_least_1_zc = false;
//crypto::secret_key asset_deploy_control_key = currency::null_skey;
currency::thirdparty_sign_handler* pthirdparty_sign_handler = nullptr;
crypto::public_key ado_current_asset_owner = currency::null_pkey;
// misc
std::string tx_meaning_for_logs; // used to correctly log things, e.g. "escrow" or "asset emission".
uint32_t additional_transfer_flags_to_mark = 0;
};
struct mode_separate_context
@ -356,7 +357,7 @@ namespace tools
uint64_t m_spent_height = 0;
uint32_t m_flags = 0;
uint64_t m_amount = 0;
boost::shared_ptr<ZC_out_info> m_zc_info_ptr;
std::shared_ptr<ZC_out_info> m_zc_info_ptr;
uint64_t amount() const { return m_amount; }
uint64_t amount_for_global_output_index() const { return is_zc() ? 0 : m_amount; } // amount value for global outputs index, it's zero for outputs with hidden amounts

View file

@ -10,7 +10,6 @@
#include <boost/serialization/deque.hpp>
#include <boost/serialization/singleton.hpp>
#include <boost/serialization/extended_type_info.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <atomic>
#include <map>

View file

@ -30,3 +30,7 @@ struct wde_construct_tx_after_asset_ownership_proof_generated
currency::asset_operation_ownership_proof* pownership_proof;
};
struct wde_construct_tx_after_asset_ownership_eth_proof_generated
{
currency::asset_operation_ownership_proof_eth* pownership_proof_eth;
};

View file

@ -13,7 +13,7 @@
#include "currency_core/offers_service_basics.h"
#include "currency_core/bc_escrow_service.h"
#include "rpc/core_rpc_server_commands_defs.h"
#include "currency_protocol/blobdatatype.h"
const uint64_t WALLET_GLOBAL_OUTPUT_INDEX_UNDEFINED = std::numeric_limits<uint64_t>::max();
@ -1982,7 +1982,7 @@ namespace wallet_public
struct COMMAND_ASSETS_DEPLOY
{
DOC_COMMAND("Deploy new asset in the system.");
struct request
{
std::list<transfer_destination> destinations;
@ -1999,16 +1999,32 @@ namespace wallet_public
struct response
{
crypto::hash result_tx;
crypto::hash tx_id;
crypto::public_key new_asset_id;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_POD_AS_HEX_STRING(result_tx) DOC_DSCR("Id of transaction that carries asset registration command, asset would be registered as soon as transaction got confirmed") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Id of transaction that carries asset registration command, asset would be registered as soon as transaction got confirmed") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(new_asset_id) DOC_DSCR("Issued asset id") DOC_EXMP("40fa6db923728b38962718c61b4dc3af1acaa1967479c73703e260dc3609c58d") DOC_END
END_KV_SERIALIZE_MAP()
};
};
// contains data for external checking & signing asset-emitting/-updating transaction by a third-party
struct data_for_external_asset_signing_tx
{
currency::blobdata unsigned_tx;
crypto::secret_key tx_secret_key;
std::vector<std::string> outputs_addresses;
currency::blobdata finalized_tx;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_BLOB_AS_BASE64_STRING(unsigned_tx) DOC_DSCR("Base64-encoded unsigned transaction blob.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(tx_secret_key) DOC_DSCR("Hex-encoded transaction secret key.") DOC_EXMP("2e0b840e70dba386effd64c5d988622dea8c064040566e6bf035034cbb54a5c08") DOC_END
KV_SERIALIZE(outputs_addresses) DOC_DSCR("Target address for each of the transaction zoutput.") DOC_EXMP_AGGR("ZxDNaMeZjwCjnHuU5gUNyrP1pM3U5vckbakzzV6dEHyDYeCpW8XGLBFTshcaY8LkG9RQn7FsQx8w2JeJzJwPwuDm2NfixPAXf", "ZxBvJDuQjMG9R2j4WnYUhBYNrwZPwuyXrC7FHdVmWqaESgowDvgfWtiXeNGu8Px9B24pkmjsA39fzSSiEQG1ekB225ZnrMTBp") DOC_END
KV_SERIALIZE_BLOB_AS_BASE64_STRING(finalized_tx)DOC_DSCR("Base64-encoded finalized_tx data structure, which should be passed along with submitting the transaction.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END
END_KV_SERIALIZE_MAP()
};
struct COMMAND_ASSETS_EMIT
{
DOC_COMMAND("Emmit new coins of the the asset, that is controlled by this wallet.");
@ -2026,13 +2042,14 @@ namespace wallet_public
END_KV_SERIALIZE_MAP()
};
struct response
{
crypto::hash result_tx;
crypto::hash tx_id;
std::optional<data_for_external_asset_signing_tx> data_for_external_signing;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_POD_AS_HEX_STRING(result_tx) DOC_DSCR("Id of transaction that carries asset registration command, asset would be registered as soon as transaction got confirmed") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Id of transaction that emits the required asset.") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE(data_for_external_signing) DOC_DSCR("[optional] Additional data for external asset tx signing.") DOC_EXMP_AGGR() DOC_END
END_KV_SERIALIZE_MAP()
};
};
@ -2054,10 +2071,12 @@ namespace wallet_public
struct response
{
crypto::hash result_tx;
crypto::hash tx_id;
std::optional<data_for_external_asset_signing_tx> data_for_external_signing;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_POD_AS_HEX_STRING(result_tx) DOC_DSCR("Id of transaction that carries asset registration command, asset would be registered as soon as transaction got confirmed") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Id of transaction that carries asset registration command, asset would be registered as soon as transaction got confirmed") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE(data_for_external_signing) DOC_DSCR("[optional] Hex-encoded transaction for external signing. ") DOC_EXMP_AGGR() DOC_END
END_KV_SERIALIZE_MAP()
};
};
@ -2079,13 +2098,47 @@ namespace wallet_public
struct response
{
crypto::hash result_tx;
crypto::hash tx_id;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_POD_AS_HEX_STRING(result_tx) DOC_DSCR("Id of transaction that carries asset burn operation") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(tx_id) DOC_DSCR("Id of transaction that carries asset burn operation") DOC_EXMP("f74bb56a5b4fa562e679ccaadd697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
END_KV_SERIALIZE_MAP()
};
};
struct COMMAND_ASSET_SEND_EXT_SIGNED_TX
{
DOC_COMMAND("Inserts externally made asset ownership signature into the given transaction and broadcasts it.");
struct request
{
currency::blobdata finalized_tx;
currency::blobdata unsigned_tx;
crypto::eth_signature eth_sig;
crypto::hash expected_tx_id;
bool unlock_transfers_on_fail = false;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE_BLOB_AS_BASE64_STRING(finalized_tx)DOC_DSCR("Base64-encoded finalized_tx data structure, which was received from emit_asset call.") DOC_EXMP("ewogICJ2ZXJzaW9uIjogMSwgC....iAgInZpbiI6IFsgewogICAgIC") DOC_END
KV_SERIALIZE_BLOB_AS_BASE64_STRING(unsigned_tx) DOC_DSCR("Base64-encoded unsigned transaction blob, which was received from emit_asset call.") DOC_EXMP("083737bcfd826a973f74bb56a52b4fa562e6579ccaadd2697463498a66de4f1760b2cd40f11c3a00a7a80000") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(eth_sig) DOC_DSCR("HEX-encoded ETH signature (64 bytes)") DOC_EXMP("674bb56a5b4fa562e679ccacc4e69455e63f4a581257382191de6856c2156630b3fba0db4bdd73ffcfb36b6add697463498a66de4f1760b2cd40f11c3a00a7a8") DOC_END
KV_SERIALIZE_POD_AS_HEX_STRING(expected_tx_id) DOC_DSCR("The expected transaction id. Tx won't be sent if the calculated one doesn't match this one. Consider using 'verified_tx_id' returned by 'decrypt_tx_details' call.") DOC_EXMP("40fa6db923728b38962718c61b4dc3af1acaa1967479c73703e260dc3609c58d") DOC_END
KV_SERIALIZE(unlock_transfers_on_fail) DOC_DSCR("If true, all locked wallet transfers, corresponding to the transaction, will be unlocked on sending failure. False by default.") DOC_EXMP(false) DOC_END
END_KV_SERIALIZE_MAP()
};
struct response
{
std::string status;
bool transfers_were_unlocked;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(status) DOC_DSCR("Status of the call") DOC_EXMP("OK") DOC_END
KV_SERIALIZE(transfers_were_unlocked) DOC_DSCR("If true, all input transfers that were locked when preparing this transaction, are now unlocked and may be spent. Can be true only upon sending failure and if requested.") DOC_EXMP(false) DOC_END
END_KV_SERIALIZE_MAP()
};
};
} // namespace wallet_rpc
} // namespace tools

View file

@ -1,4 +1,4 @@
// Copyright (c) 2014-2018 Zano Project
// Copyright (c) 2014-2024 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Copyright (c) 2012-2013 The Cryptonote developers
// Distributed under the MIT/X11 software license, see the accompanying
@ -1312,51 +1312,128 @@ namespace tools
}
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_assets_deploy(const wallet_public::COMMAND_ASSETS_DEPLOY::request& req, wallet_public::COMMAND_ASSETS_DEPLOY::response& res, epee::json_rpc::error& er, connection_context& cntx)
bool wallet_rpc_server::on_asset_deploy(const wallet_public::COMMAND_ASSETS_DEPLOY::request& req, wallet_public::COMMAND_ASSETS_DEPLOY::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
WALLET_RPC_BEGIN_TRY_ENTRY();
currency::transaction result_tx;
std::vector<currency::tx_destination_entry> currency_destinations;
rpc_destinations_to_currency_destinations(req.destinations, true, !req.do_not_split_destinations, currency_destinations);
w.get_wallet()->deploy_new_asset(req.asset_descriptor, currency_destinations, result_tx, res.new_asset_id);
res.result_tx = currency::get_transaction_hash(result_tx);
currency::finalized_tx ft{};
w.get_wallet()->deploy_new_asset(req.asset_descriptor, currency_destinations, ft, res.new_asset_id);
res.tx_id = ft.tx_id;
return true;
WALLET_RPC_CATCH_TRY_ENTRY();
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_assets_emit(const wallet_public::COMMAND_ASSETS_EMIT::request& req, wallet_public::COMMAND_ASSETS_EMIT::response& res, epee::json_rpc::error& er, connection_context& cntx)
bool wallet_rpc_server::on_asset_emit(const wallet_public::COMMAND_ASSETS_EMIT::request& req, wallet_public::COMMAND_ASSETS_EMIT::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
WALLET_RPC_BEGIN_TRY_ENTRY();
currency::transaction result_tx;
currency::asset_descriptor_base last_adb{};
CHECK_AND_ASSERT_THROW_MES(w.get_wallet()->daemon_get_asset_info(req.asset_id, last_adb), "unknown asset_id"); // TODO: bad design, consider refactoring -- sowle
std::vector<currency::tx_destination_entry> currency_destinations;
rpc_destinations_to_currency_destinations(req.destinations, true, !req.do_not_split_destinations, currency_destinations);
w.get_wallet()->emit_asset(req.asset_id, currency_destinations, result_tx);
res.result_tx = currency::get_transaction_hash(result_tx);
return true;
currency::finalized_tx ft{};
w.get_wallet()->emit_asset(req.asset_id, currency_destinations, ft);
res.tx_id = ft.tx_id;
if (last_adb.owner_eth_pub_key.has_value())
{
// include additonal info into response, if it's an external signing asset operation
wallet_public::data_for_external_asset_signing_tx data{};
data.unsigned_tx = t_serializable_object_to_blob(ft.tx);
data.tx_secret_key = ft.one_time_key;
std::vector<std::string>& outs_addr = data.outputs_addresses;
for(auto d : ft.ftp.prepared_destinations)
outs_addr.push_back(currency::get_account_address_as_str(d.addr.back()));
data.finalized_tx = t_serializable_object_to_blob(ft);
res.data_for_external_signing = data;
}
return true;
WALLET_RPC_CATCH_TRY_ENTRY();
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_assets_update(const wallet_public::COMMAND_ASSETS_UPDATE::request& req, wallet_public::COMMAND_ASSETS_UPDATE::response& res, epee::json_rpc::error& er, connection_context& cntx)
bool wallet_rpc_server::on_asset_update(const wallet_public::COMMAND_ASSETS_UPDATE::request& req, wallet_public::COMMAND_ASSETS_UPDATE::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
WALLET_RPC_BEGIN_TRY_ENTRY();
currency::transaction result_tx;
w.get_wallet()->update_asset(req.asset_id, req.asset_descriptor, result_tx);
res.result_tx = currency::get_transaction_hash(result_tx);
return true;
currency::finalized_tx ft{};
w.get_wallet()->update_asset(req.asset_id, req.asset_descriptor, ft);
res.tx_id = ft.tx_id;
if (req.asset_descriptor.owner_eth_pub_key.has_value())
{
// include additonal info into response, if it's an external signing asset operation
wallet_public::data_for_external_asset_signing_tx data{};
data.unsigned_tx = t_serializable_object_to_blob(ft.tx);
data.tx_secret_key = ft.one_time_key;
std::vector<std::string>& outs_addr = data.outputs_addresses;
for(auto d : ft.ftp.prepared_destinations)
outs_addr.push_back(currency::get_account_address_as_str(d.addr.back()));
data.finalized_tx = t_serializable_object_to_blob(ft);
res.data_for_external_signing = data;
}
return true;
WALLET_RPC_CATCH_TRY_ENTRY();
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_assets_burn(const wallet_public::COMMAND_ASSETS_BURN::request& req, wallet_public::COMMAND_ASSETS_BURN::response& res, epee::json_rpc::error& er, connection_context& cntx)
bool wallet_rpc_server::on_asset_burn(const wallet_public::COMMAND_ASSETS_BURN::request& req, wallet_public::COMMAND_ASSETS_BURN::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
WALLET_RPC_BEGIN_TRY_ENTRY();
currency::transaction result_tx;
w.get_wallet()->burn_asset(req.asset_id, req.burn_amount, result_tx);
res.result_tx = currency::get_transaction_hash(result_tx);
currency::finalized_tx ft{};
w.get_wallet()->burn_asset(req.asset_id, req.burn_amount, ft);
res.tx_id = ft.tx_id;
return true;
WALLET_RPC_CATCH_TRY_ENTRY();
}
//------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_asset_send_ext_signed_tx(const wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request& req, wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response& res, epee::json_rpc::error& er, connection_context& cntx)
{
WALLET_RPC_BEGIN_TRY_ENTRY();
currency::finalized_tx ft{};
if (!t_unserializable_object_from_blob(ft, req.finalized_tx))
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT;
er.message = "finalized_tx couldn't be deserialized";
return false;
}
if (t_serializable_object_to_blob(ft.tx) != req.unsigned_tx)
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT;
er.message = "unsigned_tx doesn't match finalized_tx";
return false;
}
crypto::hash tx_id = currency::get_transaction_hash(ft.tx);
if (req.expected_tx_id != tx_id)
{
er.code = WALLET_RPC_ERROR_CODE_WRONG_ARGUMENT;
er.message = std::string("expected_tx_id mismatch, real tx id is ") + epee::string_tools::pod_to_hex(tx_id);
return false;
}
try
{
currency::transaction result_tx{};
w.get_wallet()->submit_externally_signed_asset_tx(ft, req.eth_sig, req.unlock_transfers_on_fail, result_tx, res.transfers_were_unlocked);
}
catch(std::exception& e)
{
// doing this to be able to return 'transfers_were_unlocked' to the caller even in the case of exception
res.status = e.what();
return true;
}
return true;
WALLET_RPC_CATCH_TRY_ENTRY();
}

View file

@ -146,11 +146,11 @@ namespace tools
MAP_JON_RPC_WE("assets_whitelist_add", on_assets_whitelist_add, wallet_public::COMMAND_ASSETS_WHITELIST_ADD)
MAP_JON_RPC_WE("assets_whitelist_remove", on_assets_whitelist_remove, wallet_public::COMMAND_ASSETS_WHITELIST_REMOVE)
MAP_JON_RPC_WE("deploy_asset", on_assets_deploy, wallet_public::COMMAND_ASSETS_DEPLOY)
MAP_JON_RPC_WE("emit_asset", on_assets_emit, wallet_public::COMMAND_ASSETS_EMIT)
MAP_JON_RPC_WE("update_asset", on_assets_update, wallet_public::COMMAND_ASSETS_UPDATE)
MAP_JON_RPC_WE("burn_asset", on_assets_burn, wallet_public::COMMAND_ASSETS_BURN)
MAP_JON_RPC_WE("deploy_asset", on_asset_deploy, wallet_public::COMMAND_ASSETS_DEPLOY)
MAP_JON_RPC_WE("emit_asset", on_asset_emit, wallet_public::COMMAND_ASSETS_EMIT)
MAP_JON_RPC_WE("update_asset", on_asset_update, wallet_public::COMMAND_ASSETS_UPDATE)
MAP_JON_RPC_WE("burn_asset", on_asset_burn, wallet_public::COMMAND_ASSETS_BURN)
MAP_JON_RPC_WE("send_ext_signed_asset_tx", on_asset_send_ext_signed_tx, wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX)
//MULTIWALLET APIs
MAP_JON_RPC_WE("mw_get_wallets", on_mw_get_wallets, wallet_public::COMMAND_MW_GET_WALLETS)
@ -218,10 +218,11 @@ namespace tools
bool on_assets_whitelist_add(const wallet_public::COMMAND_ASSETS_WHITELIST_ADD::request& req, wallet_public::COMMAND_ASSETS_WHITELIST_ADD::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_assets_whitelist_remove(const wallet_public::COMMAND_ASSETS_WHITELIST_REMOVE::request& req, wallet_public::COMMAND_ASSETS_WHITELIST_REMOVE::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_assets_deploy(const wallet_public::COMMAND_ASSETS_DEPLOY::request& req, wallet_public::COMMAND_ASSETS_DEPLOY::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_assets_emit(const wallet_public::COMMAND_ASSETS_EMIT::request& req, wallet_public::COMMAND_ASSETS_EMIT::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_assets_update(const wallet_public::COMMAND_ASSETS_UPDATE::request& req, wallet_public::COMMAND_ASSETS_UPDATE::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_assets_burn(const wallet_public::COMMAND_ASSETS_BURN::request& req, wallet_public::COMMAND_ASSETS_BURN::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_asset_deploy(const wallet_public::COMMAND_ASSETS_DEPLOY::request& req, wallet_public::COMMAND_ASSETS_DEPLOY::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_asset_emit(const wallet_public::COMMAND_ASSETS_EMIT::request& req, wallet_public::COMMAND_ASSETS_EMIT::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_asset_update(const wallet_public::COMMAND_ASSETS_UPDATE::request& req, wallet_public::COMMAND_ASSETS_UPDATE::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_asset_burn(const wallet_public::COMMAND_ASSETS_BURN::request& req, wallet_public::COMMAND_ASSETS_BURN::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_asset_send_ext_signed_tx(const wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request& req, wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_mw_get_wallets(const wallet_public::COMMAND_MW_GET_WALLETS::request& req, wallet_public::COMMAND_MW_GET_WALLETS::response& res, epee::json_rpc::error& er, connection_context& cntx);
bool on_mw_select_wallet(const wallet_public::COMMAND_MW_SELECT_WALLET::request& req, wallet_public::COMMAND_MW_SELECT_WALLET::response& res, epee::json_rpc::error& er, connection_context& cntx);

View file

@ -1987,22 +1987,27 @@ void balance_via_wallet(const tools::wallet2& w, const crypto::public_key& asset
}
bool check_balance_via_wallet(const tools::wallet2& w, const char* account_name,
uint64_t expected_total, uint64_t expected_mined, uint64_t expected_unlocked, uint64_t expected_awaiting_in, uint64_t expected_awaiting_out, const crypto::public_key& asset_id /* = currency::native_coin_asset_id */)
uint64_t expected_total, uint64_t expected_mined, uint64_t expected_unlocked, uint64_t expected_awaiting_in, uint64_t expected_awaiting_out,
const crypto::public_key& asset_id /* = currency::native_coin_asset_id */, size_t asset_decimal_point /* = CURRENCY_DISPLAY_DECIMAL_POINT */)
{
uint64_t total, unlocked, awaiting_in, awaiting_out, mined;
balance_via_wallet(w, asset_id, &total, &unlocked, &awaiting_in, &awaiting_out, &mined);
std::string asset_id_str;
if (asset_id != currency::native_coin_asset_id)
{
asset_id_str = std::string(", asset_id: ") + epee::string_tools::pod_to_hex(asset_id).erase(4, 56).insert(4, "...");
if (asset_decimal_point == CURRENCY_DISPLAY_DECIMAL_POINT)
asset_decimal_point = w.get_asset_decimal_point(asset_id, asset_decimal_point);
}
LOG_PRINT_CYAN("Balance for wallet " << account_name << " @ height " << w.get_top_block_height() << asset_id_str << ":" << ENDL <<
"unlocked: " << print_money(unlocked) << ENDL <<
"awaiting in: " << print_money(awaiting_in) << ENDL <<
"awaiting out: " << print_money(awaiting_out) << ENDL <<
"mined: " << print_money(mined) << ENDL <<
"unlocked: " << print_money(unlocked, asset_decimal_point) << ENDL <<
"awaiting in: " << print_money(awaiting_in, asset_decimal_point) << ENDL <<
"awaiting out: " << print_money(awaiting_out, asset_decimal_point) << ENDL <<
"mined: " << print_money(mined, asset_decimal_point) << ENDL <<
"-----------------------------------------" << ENDL <<
"total: " << print_money(total) << ENDL,
"total: " << print_money(total, asset_decimal_point) << ENDL,
LOG_LEVEL_0);
bool r = true;
@ -2023,6 +2028,12 @@ bool check_balance_via_wallet(const tools::wallet2& w, const char* account_name,
return r;
}
bool check_balance_via_wallet(const tools::wallet2& w, const char* account_name, uint64_t expected_total, const crypto::public_key& asset_id, size_t asset_decimal_point /* = CURRENCY_DISPLAY_DECIMAL_POINT */)
{
return check_balance_via_wallet(w, account_name, expected_total, INVALID_BALANCE_VAL, INVALID_BALANCE_VAL, INVALID_BALANCE_VAL, INVALID_BALANCE_VAL, asset_id, asset_decimal_point);
}
// In assumption we have only genesis and few blocks with the same reward (==first_blocks_reward),
// this function helps to calculate such amount that many outputs have it, and amount, no output has it.
// It can fail, so check the returning value.

View file

@ -731,7 +731,9 @@ bool check_balance_via_wallet(const tools::wallet2& w, const char* account_name,
uint64_t expected_unlocked = INVALID_BALANCE_VAL,
uint64_t expected_awaiting_in = INVALID_BALANCE_VAL,
uint64_t expected_awaiting_out = INVALID_BALANCE_VAL,
const crypto::public_key& asset_id = currency::native_coin_asset_id);
const crypto::public_key& asset_id = currency::native_coin_asset_id,
size_t asset_decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT);
bool check_balance_via_wallet(const tools::wallet2& w, const char* account_name, uint64_t expected_total, const crypto::public_key& asset_id, size_t asset_decimal_point = CURRENCY_DISPLAY_DECIMAL_POINT);
bool calculate_amounts_many_outs_have_and_no_outs_have(const uint64_t first_blocks_reward, uint64_t& amount_many_outs_have, uint64_t& amount_no_outs_have);
bool find_global_index_for_output(const std::vector<test_event_entry>& events, const crypto::hash& head_block_hash, const currency::transaction& reference_tx, const size_t reference_tx_out_index, uint64_t& global_index);

View file

@ -264,13 +264,13 @@ bool generate_and_play(const char* const genclass_name, size_t hardfork_id = SIZ
if (result)
{
LOG_PRINT_GREEN(std::string(100, '=') << std::endl <<
LOG_PRINT_GREEN(std::string(72, '=') << std::endl <<
"#TEST# >>>> " << genclass_name << " <<<< Succeeded" << std::endl <<
std::string(100, '=') << std::endl, LOG_LEVEL_0 );
}
else
{
LOG_PRINT_RED( std::string(100, '=') << std::endl <<
LOG_PRINT_RED( std::string(72, '=') << std::endl <<
"#TEST# >>>> " << genclass_name << " <<<< FAILED" << std::endl <<
std::string(100, '=') << std::endl, LOG_LEVEL_0);
result = false;
@ -1293,7 +1293,9 @@ int main(int argc, char* argv[])
GENERATE_AND_PLAY_HF(asset_emission_and_unconfirmed_balance, "4-*");
GENERATE_AND_PLAY_HF(asset_operation_in_consolidated_tx, "4-*");
GENERATE_AND_PLAY_HF(asset_operation_and_hardfork_checks, "4-*");
GENERATE_AND_PLAY_HF(eth_signed_asset_basics, "4-*"); // TODO
GENERATE_AND_PLAY_HF(eth_signed_asset_via_rpc, "4-*");
GENERATE_AND_PLAY_HF(pos_fuse_test, "4-*");

View file

@ -9,6 +9,8 @@
#include "random_helper.h"
#include "wallet/wallet_debug_events_definitions.h"
#include "wallet/wallet_rpc_server.h"
#include "wallet/wallet_public_structs_defs.h"
using namespace currency;
//------------------------------------------------------------------------------
@ -1326,3 +1328,627 @@ bool asset_operation_in_consolidated_tx::assert_alice_currency_not_registered(cu
return true;
}
//------------------------------------------------------------------------------
eth_signed_asset_basics::eth_signed_asset_basics()
{
REGISTER_CALLBACK_METHOD(eth_signed_asset_basics, c1);
}
bool eth_signed_asset_basics::generate(std::vector<test_event_entry>& events) const
{
//
// Test idea: register an asset, then emit, public burn, transfer ownership, and then emit it once again.
// Update (ownership transferring) and emit operations are done using third-party external asset operation signing (ETH signature)
// Public burn operation is done by an entity, who isn't controlling the asset.
//
uint64_t ts = test_core_time::get_time();
m_accounts.resize(TOTAL_ACCS_COUNT);
account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts);
account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts);
miner_acc.generate();
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts);
DO_CALLBACK(events, "configure_core"); // default configure_core callback will initialize core runtime config with m_hardforks
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3);
DO_CALLBACK(events, "c1");
return true;
}
bool eth_signed_asset_basics::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
bool r = false;
crypto::eth_secret_key eth_sk{};
crypto::eth_public_key eth_pk{};
r = crypto::generate_eth_key_pair(eth_sk, eth_pk);
CHECK_AND_ASSERT_MES(r, false, "generate_eth_key_pair failed");
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX);
miner_wlt->refresh();
std::shared_ptr<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX);
alice_wlt->refresh();
//
// register
//
asset_descriptor_base adb{};
adb.decimal_point = 2;
adb.total_max_supply = 10000;
adb.full_name = "Either";
adb.ticker = "EITH";
adb.owner_eth_pub_key = eth_pk; // note setting owner eth pub key here
uint64_t initial_emit_amount = adb.total_max_supply / 2;
std::vector<tx_destination_entry> destinations{tx_destination_entry{initial_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey}};
finalized_tx ft{};
crypto::public_key asset_id = currency::null_pkey;
miner_wlt->deploy_new_asset(adb, destinations, ft, asset_id);
const transaction& tx = ft.tx;
LOG_PRINT_L0("Deployed new asset: " << asset_id << ", tx_id: " << ft.tx_id);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
miner_wlt->refresh();
//
// emit
//
destinations.clear();
destinations.emplace_back(initial_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); // asset
destinations.emplace_back(MK_TEST_COINS(100), m_accounts[ALICE_ACC_IDX].get_public_address()); // some native coins for fee
uint64_t emit_amount = adb.total_max_supply - initial_emit_amount;
ft = finalized_tx{};
miner_wlt->emit_asset(asset_id, destinations, ft);
// make sure the transaction didn't get into tx pool (as it's not fully signed)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// make sure emit transaction cannot be added to the transaction pool (it's not fully signed atm)
tx_verification_context tvc{};
r = c.get_tx_pool().add_tx(ft.tx, tvc, false);
CHECK_AND_ASSERT_MES(!r, false, "unsigned emit tx was able to be added to the pool");
// sign asset emit operation with ETH signature
crypto::eth_signature eth_sig{};
r = crypto::generate_eth_signature(ft.tx_id, eth_sk, eth_sig);
CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature failed");
transaction emit_tx{};
bool transfers_unlocked = false;
miner_wlt->submit_externally_signed_asset_tx(ft, eth_sig, true, emit_tx, transfers_unlocked);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// Alice checks her asset balance
size_t blocks_fetched = 0;
alice_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 2);
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", adb.total_max_supply, asset_id, adb.decimal_point), false, "");
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(100)), false, "");
//
// public burn (by Alice, as anyone can do public burn)
//
uint64_t amount_to_burn = initial_emit_amount / 2;
uint64_t total_amount_expected = adb.total_max_supply - amount_to_burn;
r = mine_next_pow_blocks_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c, CURRENCY_MINED_MONEY_UNLOCK_WINDOW);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
miner_wlt->refresh();
alice_wlt->refresh();
ft = finalized_tx{};
alice_wlt->burn_asset(asset_id, amount_to_burn, ft);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
asset_descriptor_base adb_recent{};
r = c.get_blockchain_storage().get_asset_info(asset_id, adb_recent);
CHECK_AND_ASSERT_MES(r, false, "get_asset_info failed");
// make sure the current supply of the asset did change accordingly
CHECK_AND_ASSERT_EQ(adb_recent.current_supply, total_amount_expected);
//
// ownership transfer (to another ETH key)
//
crypto::eth_secret_key eth_sk_2{};
crypto::eth_public_key eth_pk_2{};
r = crypto::generate_eth_key_pair(eth_sk_2, eth_pk_2);
CHECK_AND_ASSERT_MES(r, false, "generate_eth_key_pair failed");
ft = finalized_tx{};
miner_wlt->transfer_asset_ownership(asset_id, eth_pk_2, ft);
// make sure the transaction didn't get into tx pool (as it's not fully signed)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// make sure ownership transaction cannot be added to the transaction pool (it's not fully signed atm)
tvc = tx_verification_context{};
r = c.get_tx_pool().add_tx(ft.tx, tvc, false);
CHECK_AND_ASSERT_MES(!r, false, "unsigned tx was able to be added to the pool");
// sign asset emit operation with ETH signature
eth_sig = crypto::eth_signature{};
r = crypto::generate_eth_signature(ft.tx_id, eth_sk, eth_sig); // signing with old eth key
CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature failed");
transaction to_tx{};
transfers_unlocked = false;
miner_wlt->submit_externally_signed_asset_tx(ft, eth_sig, true, to_tx, transfers_unlocked);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
miner_wlt->refresh();
//
// emit #2
//
emit_amount = adb.total_max_supply - total_amount_expected; // up to max
destinations.clear();
destinations.emplace_back(emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address(), null_pkey); // asset
ft = finalized_tx{};
miner_wlt->emit_asset(asset_id, destinations, ft);
// make sure the transaction didn't get into tx pool (as it's not fully signed)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// make sure emit transaction cannot be added to the transaction pool (it's not fully signed atm)
r = c.get_tx_pool().add_tx(ft.tx, tvc, false);
CHECK_AND_ASSERT_MES(!r, false, "unsigned emit tx was able to be added to the pool");
// sign asset emit operation with ETH signature
eth_sig = crypto::eth_signature{};
r = crypto::generate_eth_signature(ft.tx_id, eth_sk_2, eth_sig); // note using ETH key #2 here
CHECK_AND_ASSERT_MES(r, false, "generate_eth_signature failed");
emit_tx = transaction{};
transfers_unlocked = false;
miner_wlt->submit_externally_signed_asset_tx(ft, eth_sig, true, emit_tx, transfers_unlocked);
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
r = mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c);
CHECK_AND_ASSERT_MES(r, false, "mine_next_pow_block_in_playtime failed");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// Alice checks her asset balance
alice_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 3);
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", adb.total_max_supply, asset_id, adb.decimal_point), false, "");
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", MK_TEST_COINS(100) - TESTS_DEFAULT_FEE), false, "");
return true;
}
//------------------------------------------------------------------------------
eth_signed_asset_via_rpc::eth_signed_asset_via_rpc()
{
REGISTER_CALLBACK_METHOD(eth_signed_asset_via_rpc, c1);
}
bool eth_signed_asset_via_rpc::generate(std::vector<test_event_entry>& events) const
{
//
// Test idea: make sure register, emit, and ownership transfer operations with external signing (ETH signature)
// can be done entirely using JSON RPC calls (both, the core RPC and the wallet RPC)
//
uint64_t ts = test_core_time::get_time();
m_accounts.resize(TOTAL_ACCS_COUNT);
account_base& miner_acc = m_accounts[MINER_ACC_IDX]; miner_acc.generate(); miner_acc.set_createtime(ts);
account_base& alice_acc = m_accounts[ALICE_ACC_IDX]; alice_acc.generate(); alice_acc.set_createtime(ts);
account_base& bob_acc = m_accounts[BOB_ACC_IDX]; bob_acc.generate(); bob_acc.set_createtime(ts);
miner_acc.generate();
MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, ts);
DO_CALLBACK(events, "configure_core"); // default configure_core callback will initialize core runtime config with m_hardforks
REWIND_BLOCKS_N_WITH_TIME(events, blk_0r, blk_0, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 3);
DO_CALLBACK(events, "c1");
return true;
}
bool eth_signed_asset_via_rpc::c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events)
{
bool r = false;
crypto::eth_secret_key eth_sk{};
crypto::eth_public_key eth_pk{};
r = crypto::generate_eth_key_pair(eth_sk, eth_pk);
CHECK_AND_ASSERT_MES(r, false, "generate_eth_key_pair failed");
std::shared_ptr<tools::wallet2> miner_wlt = init_playtime_test_wallet(events, c, MINER_ACC_IDX);
std::shared_ptr<tools::wallet2> alice_wlt = init_playtime_test_wallet(events, c, ALICE_ACC_IDX);
miner_wlt->refresh();
alice_wlt->refresh();
// wallet RPC server
tools::wallet_rpc_server miner_wlt_rpc(miner_wlt);
epee::json_rpc::error jerr{};
tools::wallet_rpc_server::connection_context ctx{};
// core RPC server
currency::t_currency_protocol_handler<currency::core> m_cprotocol(c, nullptr);
nodetool::node_server<currency::t_currency_protocol_handler<currency::core> > p2p(m_cprotocol);
bc_services::bc_offers_service of(nullptr);
currency::core_rpc_server core_rpc_wrapper(c, p2p, of);
core_rpc_wrapper.set_ignore_connectivity_status(true);
// asset description
asset_descriptor_base adb{};
adb.decimal_point = 2;
adb.total_max_supply = 45000;
adb.full_name = "P450";
adb.ticker = "P450";
adb.owner_eth_pub_key = eth_pk; // note setting owner eth pub key here
uint64_t initial_register_amount = 10000;
// 1. Miner deploys initial amount of the asset (all go to Alice)
// deploy operation don't require eth proof and therefore the corresponding tx will be generated and added to the tx pool as usual
tools::wallet_public::COMMAND_ASSETS_DEPLOY::request deploy_req{};
deploy_req.asset_descriptor = adb;
deploy_req.destinations.push_back(tools::wallet_public::transfer_destination{initial_register_amount, m_accounts[ALICE_ACC_IDX].get_public_address_str(), null_pkey});
deploy_req.do_not_split_destinations = false;
tools::wallet_public::COMMAND_ASSETS_DEPLOY::response deploy_resp{};
r = miner_wlt_rpc.on_asset_deploy(deploy_req, deploy_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC on_asset_deploy failed");
const crypto::public_key asset_id = deploy_resp.new_asset_id;
LOG_PRINT_GREEN_L0("Asset " << asset_id << " was successfully deployed with tx " << deploy_resp.tx_id);
// make sure tx was added to the pool
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// confirm the tx with a block
CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, "");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
size_t blocks_fetched = 0;
miner_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 1);
// Alice checks her asset balance
alice_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 1);
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", initial_register_amount, asset_id, adb.decimal_point), false, "");
//
// 2. Miner emits additional amount of the asset and transfers it to Alice
//
uint64_t additional_emit_amount = 20000;
uint64_t total_asset_amount = initial_register_amount + additional_emit_amount;
tools::wallet_public::COMMAND_ASSETS_EMIT::request emit_req{};
emit_req.asset_id = asset_id;
emit_req.destinations.push_back(tools::wallet_public::transfer_destination{additional_emit_amount, m_accounts[ALICE_ACC_IDX].get_public_address_str(), asset_id});
emit_req.do_not_split_destinations = false;
tools::wallet_public::COMMAND_ASSETS_EMIT::response emit_resp{};
r = miner_wlt_rpc.on_asset_emit(emit_req, emit_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC on_asset_emit failed: " << jerr.message);
// make sure tx was NOT added to the pool (because it's only partially signed)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// unserialize transaction that we got from RPC
transaction emit_tx{};
CHECK_AND_ASSERT_MES(emit_resp.data_for_external_signing.has_value(), false, "data_for_external_signing has no value");
r = t_unserializable_object_from_blob(emit_tx, emit_resp.data_for_external_signing->unsigned_tx);
CHECK_AND_ASSERT_MES(r, false, "t_unserializable_object_from_blob failed");
CHECK_AND_ASSERT_EQ(get_transaction_hash(emit_tx), emit_resp.tx_id);
// make sure emit transaction cannot be added to the transaction pool (it's not fully signed atm)
tx_verification_context tvc{};
r = c.get_tx_pool().add_tx(emit_tx, tvc, false);
CHECK_AND_ASSERT_MES(!r, false, "emit tx was able to be added to the pool");
//
// decrypt emission transaction outputs prior to ETH signing to make sure it's valid
//
currency::COMMAND_RPC_DECRYPT_TX_DETAILS::request decrypt_req{};
decrypt_req.tx_secret_key = emit_resp.data_for_external_signing->tx_secret_key;
decrypt_req.tx_blob = epee::string_encoding::base64_encode(emit_resp.data_for_external_signing->unsigned_tx);
// note: decrypt_req.outputs_addresses can be populated using emit_resp.data_for_external_signing->outputs_addresses but we fill it manually here
decrypt_req.outputs_addresses.push_back(m_accounts[MINER_ACC_IDX].get_public_address_str()); // we expect that the first output is the cashback and addressed to miner
for(size_t i = 0, size = emit_tx.vout.size() - 1; i < size; ++i)
decrypt_req.outputs_addresses.push_back(m_accounts[ALICE_ACC_IDX].get_public_address_str()); // we expect all other outputs are asset emission and addresses to Alice
currency::COMMAND_RPC_DECRYPT_TX_DETAILS::response decrypt_resp{};
r = core_rpc_wrapper.on_decrypt_tx_details(decrypt_req, decrypt_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC on_decrypt_tx_details failed: " << jerr.message);
// make sure that verified_tx_id is the one we expect
CHECK_AND_ASSERT_EQ(decrypt_resp.verified_tx_id, emit_resp.tx_id);
// after a successfull tx outputs decryption, examine them
CHECK_AND_ASSERT_EQ(decrypt_resp.decoded_outputs.size(), emit_tx.vout.size());
uint64_t decrypted_emission_sum = 0;
for(auto& el : decrypt_resp.decoded_outputs)
{
if (el.asset_id == asset_id)
decrypted_emission_sum += el.amount;
else
CHECK_AND_ASSERT_EQ(el.asset_id, native_coin_asset_id);
}
// make sure the transaction emits the expected amount of asset
CHECK_AND_ASSERT_EQ(decrypted_emission_sum, additional_emit_amount);
LOG_PRINT_YELLOW(decrypt_resp.tx_in_json, LOG_LEVEL_0); // tx with still missing ownership proof
//
// as everything is allright, sign emit_tx with ETH signature.
//
crypto::eth_signature eth_sig{};
crypto::generate_eth_signature(emit_resp.tx_id, eth_sk, eth_sig);
// instant verification, just in case
r = crypto::verify_eth_signature(emit_resp.tx_id, eth_pk, eth_sig);
CHECK_AND_ASSERT_MES(r, false, "verify_eth_signature failed");
//
// send ETH signature alogn with all previous data to a wallet RPC call for final tx assembling and broadcasting
//
tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request send_signed_req{};
send_signed_req.unsigned_tx = emit_resp.data_for_external_signing->unsigned_tx;
send_signed_req.eth_sig = eth_sig;
send_signed_req.expected_tx_id = decrypt_resp.verified_tx_id;
send_signed_req.finalized_tx = emit_resp.data_for_external_signing->finalized_tx;
send_signed_req.unlock_transfers_on_fail = true;
tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response send_signed_resp{};
r = miner_wlt_rpc.on_asset_send_ext_signed_tx(send_signed_req, send_signed_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: " << jerr.message);
// make sure tx was broadcasted
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// confirm the tx with a block
CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, "");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// Alice checks her asset balance
alice_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 1);
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, "");
//
// 3. Miner transfer ownership of the asset to another ETH key (#2)
//
crypto::eth_secret_key eth_sk_2{};
crypto::eth_public_key eth_pk_2{};
r = crypto::generate_eth_key_pair(eth_sk_2, eth_pk_2);
CHECK_AND_ASSERT_MES(r, false, "generate_eth_key_pair failed");
tools::wallet_public::COMMAND_ASSETS_UPDATE::request to_req{}; // 'to' means transfer of ownership
to_req.asset_id = asset_id;
{
// request the most recent asset info from core to correctly fill to_req
currency::COMMAND_RPC_GET_ASSET_INFO::request req{};
req.asset_id = asset_id;
currency::COMMAND_RPC_GET_ASSET_INFO::response resp{};
CHECK_AND_ASSERT_MES(core_rpc_wrapper.on_get_asset_info(req, resp, ctx), false, "on_get_asset_info failed");
to_req.asset_descriptor = resp.asset_descriptor;
}
to_req.asset_descriptor.owner_eth_pub_key = eth_pk_2; // new owner, note using another ETH pub key
to_req.asset_descriptor.meta_info = "owner: eth_pk_2"; // it's also possible to change meta_info with this update operation
tools::wallet_public::COMMAND_ASSETS_UPDATE::response to_resp{};
r = miner_wlt_rpc.on_asset_update(to_req, to_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: " << jerr.message);
// make sure tx was NOT added to the pool (because it's only partially signed)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
CHECK_AND_ASSERT_MES(to_resp.data_for_external_signing.has_value(), false, "data_for_external_signing has no value");
transaction to_tx{};
r = t_unserializable_object_from_blob(to_tx, to_resp.data_for_external_signing->unsigned_tx);
CHECK_AND_ASSERT_MES(r, false, "t_unserializable_object_from_blob failed");
CHECK_AND_ASSERT_EQ(get_transaction_hash(to_tx), to_resp.tx_id);
// make sure ownership transferring transaction cannot be added to the transaction pool (it's not fully signed atm)
tvc = tx_verification_context{};
r = c.get_tx_pool().add_tx(to_tx, tvc, false);
CHECK_AND_ASSERT_MES(!r, false, "ownership transferring tx was able to be added to the pool");
//
// decrypt ownership transfer transaction prior to ETH signing to make sure it's valid
//
decrypt_req = currency::COMMAND_RPC_DECRYPT_TX_DETAILS::request{};
decrypt_req.tx_secret_key = to_resp.data_for_external_signing->tx_secret_key;
decrypt_req.tx_blob = epee::string_encoding::base64_encode(to_resp.data_for_external_signing->unsigned_tx);
// note: decrypt_req.outputs_addresses can be populated using to_resp.data_for_external_signing->outputs_addresses but we fill it manually here
for(size_t i = 0, size = to_tx.vout.size(); i < size; ++i)
decrypt_req.outputs_addresses.push_back(m_accounts[MINER_ACC_IDX].get_public_address_str()); // we expect all outputs goes to Miner
decrypt_resp = currency::COMMAND_RPC_DECRYPT_TX_DETAILS::response{};
r = core_rpc_wrapper.on_decrypt_tx_details(decrypt_req, decrypt_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC on_decrypt_tx_details failed: " << jerr.message);
// make sure that verified_tx_id is the one we expect
CHECK_AND_ASSERT_EQ(decrypt_resp.verified_tx_id, to_resp.tx_id);
// after a successfull tx outputs decryption, examine them
CHECK_AND_ASSERT_EQ(decrypt_resp.decoded_outputs.size(), to_tx.vout.size());
uint64_t asset_sum = 0;
for(auto& el : decrypt_resp.decoded_outputs)
{
if (el.asset_id != native_coin_asset_id)
asset_sum += el.amount;
}
// make sure the transaction don't send assets
CHECK_AND_ASSERT_EQ(asset_sum, 0);
// make sure this is an ownership transfer transaction and the ownership is correctly transferred:
// Note: this check could also be done by examination of decrypt_resp.tx_in_json
asset_descriptor_operation* pado = get_type_in_variant_container<asset_descriptor_operation>(to_tx.extra);
CHECK_AND_ASSERT_NEQ(pado, 0);
CHECK_AND_ASSERT_EQ(pado->operation_type, ASSET_DESCRIPTOR_OPERATION_UPDATE);
CHECK_AND_ASSERT_EQ(pado->opt_asset_id.has_value(), true);
CHECK_AND_ASSERT_EQ(pado->opt_asset_id.get(), asset_id);
CHECK_AND_ASSERT_EQ(pado->descriptor.owner_eth_pub_key.has_value(), true);
CHECK_AND_ASSERT_EQ(pado->descriptor.owner_eth_pub_key.get(), eth_pk_2); // the most important condition for an ownership transfer
// other fileds of pado->descriptor may also be checked here
//
// as everything is allright, sign to_tx with ETH signature.
//
crypto::eth_signature to_eth_sig{};
crypto::generate_eth_signature(to_resp.tx_id, eth_sk, to_eth_sig); // note using old ETH secret key here, because this tx must be signed with the original owner
// instant verification, just in case
r = crypto::verify_eth_signature(to_resp.tx_id, eth_pk, to_eth_sig);
CHECK_AND_ASSERT_MES(r, false, "verify_eth_signature failed");
//
// send ETH signature along with all previous data to a wallet RPC call for final tx assembling and broadcasting
//
send_signed_req = tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request{};
send_signed_req.unsigned_tx = to_resp.data_for_external_signing->unsigned_tx;
send_signed_req.eth_sig = to_eth_sig;
send_signed_req.expected_tx_id = decrypt_resp.verified_tx_id;
send_signed_req.finalized_tx = to_resp.data_for_external_signing->finalized_tx;
send_signed_req.unlock_transfers_on_fail = true;
send_signed_resp = tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response{};
r = miner_wlt_rpc.on_asset_send_ext_signed_tx(send_signed_req, send_signed_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: " << jerr.message);
// make sure tx was broadcasted
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// confirm the tx with a block
CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, "");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// Alice checks her asset balance, it shouldn't change
alice_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 1);
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", total_asset_amount, asset_id, adb.decimal_point), false, "");
miner_wlt->refresh();
//
// 4. Miner emits additional amount of the asset (signing it with ETH key #2) and transfers it to Bob
//
additional_emit_amount = 15000;
uint64_t alice_amount = total_asset_amount;
total_asset_amount += additional_emit_amount;
emit_req = tools::wallet_public::COMMAND_ASSETS_EMIT::request{};
emit_req.asset_id = asset_id;
emit_req.destinations.push_back(tools::wallet_public::transfer_destination{additional_emit_amount, m_accounts[BOB_ACC_IDX].get_public_address_str(), asset_id});
emit_req.do_not_split_destinations = false;
emit_resp = tools::wallet_public::COMMAND_ASSETS_EMIT::response{};
r = miner_wlt_rpc.on_asset_emit(emit_req, emit_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC on_asset_emit failed: " << jerr.message);
// make sure tx was NOT added to the pool (because it's only partially signed)
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// unserialize transaction that we got from RPC
emit_tx = transaction{};
CHECK_AND_ASSERT_MES(emit_resp.data_for_external_signing.has_value(), false, "data_for_external_signing has no value");
r = t_unserializable_object_from_blob(emit_tx, emit_resp.data_for_external_signing->unsigned_tx);
CHECK_AND_ASSERT_MES(r, false, "t_unserializable_object_from_blob failed");
CHECK_AND_ASSERT_EQ(get_transaction_hash(emit_tx), emit_resp.tx_id);
// make sure emit transaction cannot be added to the transaction pool (it's not fully signed atm)
tvc = tx_verification_context{};
r = c.get_tx_pool().add_tx(emit_tx, tvc, false);
CHECK_AND_ASSERT_MES(!r, false, "emit tx was able to be added to the pool");
//
// decrypt emission transaction outputs prior to ETH signing to make sure it's valid
//
decrypt_req = currency::COMMAND_RPC_DECRYPT_TX_DETAILS::request{};
decrypt_req.tx_secret_key = emit_resp.data_for_external_signing->tx_secret_key;
decrypt_req.tx_blob = epee::string_encoding::base64_encode(emit_resp.data_for_external_signing->unsigned_tx);
// note: decrypt_req.outputs_addresses can be populated using emit_resp.data_for_external_signing->outputs_addresses but we fill it manually here
decrypt_req.outputs_addresses.push_back(m_accounts[MINER_ACC_IDX].get_public_address_str()); // we expect that the first output is the cashback and addressed to miner
for(size_t i = 0, size = emit_tx.vout.size() - 1; i < size; ++i)
decrypt_req.outputs_addresses.push_back(m_accounts[BOB_ACC_IDX].get_public_address_str()); // we expect all other outputs are asset emission and addresses to Bob
decrypt_resp = currency::COMMAND_RPC_DECRYPT_TX_DETAILS::response{};
r = core_rpc_wrapper.on_decrypt_tx_details(decrypt_req, decrypt_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC on_decrypt_tx_details failed: " << jerr.message);
// make sure that verified_tx_id is the one we expect
CHECK_AND_ASSERT_EQ(decrypt_resp.verified_tx_id, emit_resp.tx_id);
// after a successfull tx outputs decryption, examine them
CHECK_AND_ASSERT_EQ(decrypt_resp.decoded_outputs.size(), emit_tx.vout.size());
decrypted_emission_sum = 0;
for(auto& el : decrypt_resp.decoded_outputs)
{
if (el.asset_id == asset_id)
decrypted_emission_sum += el.amount;
else
CHECK_AND_ASSERT_EQ(el.asset_id, native_coin_asset_id);
}
// make sure the transaction emits the expected amount of asset
CHECK_AND_ASSERT_EQ(decrypted_emission_sum, additional_emit_amount);
//
// as everything is allright, sign emit_tx with ETH signature.
//
eth_sig = crypto::eth_signature{};
crypto::generate_eth_signature(emit_resp.tx_id, eth_sk_2, eth_sig); // note using ETH key #2
// instant verification, just in case
r = crypto::verify_eth_signature(emit_resp.tx_id, eth_pk_2, eth_sig);
CHECK_AND_ASSERT_MES(r, false, "verify_eth_signature failed");
//
// send ETH signature alogn with all previous data to a wallet RPC call for final tx assembling and broadcasting
//
send_signed_req = tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::request{};
send_signed_req.unsigned_tx = emit_resp.data_for_external_signing->unsigned_tx;
send_signed_req.eth_sig = eth_sig;
send_signed_req.expected_tx_id = decrypt_resp.verified_tx_id;
send_signed_req.finalized_tx = emit_resp.data_for_external_signing->finalized_tx;
send_signed_req.unlock_transfers_on_fail = true;
send_signed_resp = tools::wallet_public::COMMAND_ASSET_SEND_EXT_SIGNED_TX::response{};
r = miner_wlt_rpc.on_asset_send_ext_signed_tx(send_signed_req, send_signed_resp, jerr, ctx);
CHECK_AND_ASSERT_MES(r, false, "RPC send_ext_signed_asset_tx failed: " << jerr.message);
// make sure tx was broadcasted
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 1, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// confirm the tx with a block
CHECK_AND_ASSERT_MES(mine_next_pow_block_in_playtime(m_accounts[MINER_ACC_IDX].get_public_address(), c), false, "");
CHECK_AND_ASSERT_MES(c.get_pool_transactions_count() == 0, false, "Unexpected number of txs in the pool: " << c.get_pool_transactions_count());
// Alice checks her asset balance, it shouldn't change
alice_wlt->refresh(blocks_fetched);
CHECK_AND_ASSERT_EQ(blocks_fetched, 1);
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*alice_wlt, "Alice", alice_amount, asset_id, adb.decimal_point), false, "");
// Bob checks his asset balance
std::shared_ptr<tools::wallet2> bob_wlt = init_playtime_test_wallet(events, c, BOB_ACC_IDX);
bob_wlt->refresh();
CHECK_AND_ASSERT_MES(check_balance_via_wallet(*bob_wlt, "Bob", additional_emit_amount, asset_id, adb.decimal_point), false, "");
// finally, check asset's current supply
asset_descriptor_base adb_temp{};
r = c.get_blockchain_storage().get_asset_info(asset_id, adb_temp);
CHECK_AND_ASSERT_MES(r, false, "get_asset_info failed");
CHECK_AND_ASSERT_EQ(adb_temp.current_supply, total_asset_amount);
return true;
}

View file

@ -1,4 +1,4 @@
// Copyright (c) 2014-2023 Zano Project
// Copyright (c) 2014-2024 Zano Project
// Copyright (c) 2014-2018 The Louisdor Project
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@ -81,3 +81,17 @@ private:
mutable currency::asset_descriptor_base m_adb_alice_currency{};
mutable currency::asset_descriptor_operation m_ado_alice_currency{};
};
struct eth_signed_asset_basics : public wallet_test
{
eth_signed_asset_basics();
bool generate(std::vector<test_event_entry>& events) const;
bool c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
};
struct eth_signed_asset_via_rpc : public wallet_test
{
eth_signed_asset_via_rpc();
bool generate(std::vector<test_event_entry>& events) const;
bool c1(currency::core& c, size_t ev_index, const std::vector<test_event_entry>& events);
};

View file

@ -29,6 +29,9 @@ struct wallet_test : virtual public test_chain_unit_enchanced
template<typename wallet_t>
std::shared_ptr<wallet_t> init_playtime_test_wallet_t(const std::vector<test_event_entry>& events, currency::core& c, const currency::account_base& acc, bool true_http_rpc = false) const
{
#define LOCAL_HOST_CSTR "127.0.0.1"
#define LOCAL_PORT_CSTR "33777"
CHECK_AND_ASSERT_THROW_MES(events.size() > 0 && events[0].type() == typeid(currency::block), "Invalid events queue, can't find genesis block at the beginning");
crypto::hash genesis_hash = get_block_hash(boost::get<currency::block>(events[0]));
@ -36,14 +39,14 @@ struct wallet_test : virtual public test_chain_unit_enchanced
{
m_core_proxy = std::make_shared<tools::default_http_core_proxy>();
m_core_proxy->set_connectivity(100, 1);
CHECK_AND_ASSERT_THROW_MES(m_core_proxy->set_connection_addr("127.0.0.1:33777"), "set_connection_addr failed");
CHECK_AND_ASSERT_THROW_MES(m_core_proxy->set_connection_addr(LOCAL_HOST_CSTR ":" LOCAL_PORT_CSTR), "set_connection_addr failed");
if (!m_core_proxy->check_connection())
{
// if there's not http rpc core server yet, create one
boost::program_options::options_description desc_options;
currency::core_rpc_server::init_options(desc_options);
boost::program_options::variables_map vm{};
char* argv[] = {"--rpc-bind-ip=127.0.0.1", "--rpc-bind-port=33777"};
const char* const argv[] = {"--rpc-bind-ip=" LOCAL_HOST_CSTR, "--rpc-bind-port=" LOCAL_PORT_CSTR};
boost::program_options::store(boost::program_options::parse_command_line(sizeof argv / sizeof argv[0], argv, desc_options), vm);
m_http_rpc_server = std::make_shared<core_http_rpc_server_details>(c, vm);
}
@ -58,6 +61,9 @@ struct wallet_test : virtual public test_chain_unit_enchanced
w->set_core_proxy(m_core_proxy);
w->set_disable_tor_relay(true);
return w;
#undef LOCAL_HOST_CSTR
#undef LOCAL_PORT_CSTR
}

View file

@ -20,6 +20,7 @@
#include "crypto/range_proofs.h"
#include "../core_tests/random_helper.h"
#include "crypto_torsion_elements.h"
#include "crypto/eth_signature.h"
using namespace crypto;
@ -1896,6 +1897,93 @@ TEST(crypto, generators_precomp)
#undef CHECK_PRECOMP
}
#include "bitcoin-secp256k1/include/secp256k1.h"
TEST(crypto, secp256k1_ecdsa_native)
{
bool r = false;
secp256k1_context* ctx = secp256k1_context_create(SECP256K1_CONTEXT_NONE);
uint8_t randomness[32];
crypto::generate_random_bytes(sizeof randomness, randomness);
secp256k1_context_randomize(ctx, randomness);
uint8_t seckey[32] = {};
while(true)
{
crypto::generate_random_bytes(sizeof seckey, seckey);
if (secp256k1_ec_seckey_verify(ctx, seckey))
break;
}
secp256k1_pubkey pubkey{};
ASSERT_TRUE(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey));
uint8_t compressed_pubkey[33] = {};
size_t output_len = sizeof compressed_pubkey;
ASSERT_TRUE(secp256k1_ec_pubkey_serialize(ctx, compressed_pubkey, &output_len, &pubkey, SECP256K1_EC_COMPRESSED));
ASSERT_TRUE(output_len == sizeof compressed_pubkey);
secp256k1_ecdsa_signature secp256k1_ecdsa_sig{};
hash msg_hash = hash_helper_t::h("message");
ASSERT_TRUE(secp256k1_ecdsa_sign(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)&msg_hash, seckey, NULL, NULL));
// Serialize the signature in a compact form.
unsigned char secp256k1_ecdsa_sig_serialized[64] = {};
ASSERT_TRUE(secp256k1_ecdsa_signature_serialize_compact(ctx, secp256k1_ecdsa_sig_serialized, &secp256k1_ecdsa_sig));
//
// Verification
//
secp256k1_ecdsa_sig = secp256k1_ecdsa_signature{};
pubkey = secp256k1_pubkey{};
// Deserialize the signature.
ASSERT_TRUE(secp256k1_ecdsa_signature_parse_compact(ctx, &secp256k1_ecdsa_sig, secp256k1_ecdsa_sig_serialized));
// Deserialize the public key. This will return 0 if the public key can't be parsed correctly. */
ASSERT_TRUE(secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey)));
// verify a signature
ASSERT_TRUE(secp256k1_ecdsa_verify(ctx, &secp256k1_ecdsa_sig, (const unsigned char*)&msg_hash, &pubkey));
// verify using a static context
ASSERT_TRUE(secp256k1_ecdsa_verify(secp256k1_context_static, &secp256k1_ecdsa_sig, (const unsigned char*)&msg_hash, &pubkey));
// Epilogue
secp256k1_context_destroy(ctx);
return true;
}
TEST(crypto, eth_signature_basics)
{
eth_secret_key sk{};
eth_public_key pk{};
ASSERT_TRUE(generate_eth_key_pair(sk, pk));
eth_signature sig{};
hash m = hash_helper_t::h("How many of you have ever felt personally victimized by elliptic curves?");
ASSERT_TRUE(generate_eth_signature(m, sk, sig));
const eth_signature const_sig = sig;
ASSERT_TRUE(verify_eth_signature(m, pk, const_sig));
for(size_t i = 0; i < sizeof sig; ++i)
{
eth_signature bad_sig = sig;
bad_sig.data[i] ^= 1 + (rand() % 254); // xor with a number fom [1; 255] to make sure this byte will change
ASSERT_FALSE(verify_eth_signature(m, pk, bad_sig));
}
return true;
}
//