diff --git a/CMakeLists.txt b/CMakeLists.txt index e2651c5d..56d249d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -77,6 +77,9 @@ if(TESTNET) add_definitions(-DTESTNET) endif() +set(OPENSSL_USE_STATIC_LIBS TRUE) # link statically +find_package(OpenSSL REQUIRED) + if(DISABLE_TOR) message("NOTICE: Building with disabled TOR support!") add_definitions(-DDISABLE_TOR) diff --git a/README.md b/README.md index c6ad2f56..13558e5f 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ Be sure to clone the repository properly:\ ### Dependencies | component / version | minimum
(not recommended but may work) | recommended | most recent of what we have ever tested | |--|--|--|--| -| gcc (Linux) | 5.4.0 | 7.4.0 | 8.3.0 | +| gcc (Linux) | 5.4.0 | 7.5.0 | 8.3.0 | | llvm/clang (Linux) | UNKNOWN | 7.0.1 | 8.0.0 | -| [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2015 (14.0 update 1) | 2017 (15.9.0) | 2019 | +| [MSVC](https://visualstudio.microsoft.com/downloads/) (Windows) | 2015 (14.0 update 1) | 2017 (15.9.0) | 2022 (17.4.2) | | [XCode](https://developer.apple.com/downloads/) (macOS) | 9.2 | 12.3 | 12.3 | -| [CMake](https://cmake.org/download/) | 2.8.6 | 3.15.5 | 3.20 | +| [CMake](https://cmake.org/download/) | 2.8.6 | 3.15.5 | 3.26.3 | | [Boost](https://www.boost.org/users/download/) | 1.70 | 1.70 | 1.76 | | [OpenSSL](https://www.openssl.org/source/) [(win)](https://slproweb.com/products/Win32OpenSSL.html) | - | 1.1.1n | 1.1.1n | | [Qt](https://download.qt.io/archive/qt/) (*only for GUI*) | 5.8.0 | 5.11.2 | 5.15.2 | @@ -45,7 +45,7 @@ Recommended OS version: Ubuntu 18.04 LTS. 2. Download and build Boost curl -OL https://boostorg.jfrog.io/artifactory/main/release/1.70.0/source/boost_1_70_0.tar.bz2 - tar -xjf boost_1_70_0.tar.bz2 + echo "430ae8354789de4fd19ee52f3b1f739e1fba576f0aded0897c3c2bc00fb38778 boost_1_70_0.tar.bz2" | shasum -c && tar -xjf boost_1_70_0.tar.bz2 cd boost_1_70_0 ./bootstrap.sh --with-libraries=system,filesystem,thread,date_time,chrono,regex,serialization,atomic,program_options,locale,timer,log ./b2 @@ -63,10 +63,10 @@ Recommended OS version: Ubuntu 18.04 LTS. 4. Install OpenSSL - We recommend installing OpenSSL v1.1.1n locally unless you would like to use the same version system-wide. + We recommend installing OpenSSL v1.1.1n locally unless you would like to use the same version system-wide. Adjust the local path `/home/user/openssl` in the commands below according to your needs. curl -OL https://www.openssl.org/source/openssl-1.1.1n.tar.gz - tar xaf openssl-1.1.1n.tar.gz + echo "40dceb51a4f6a5275bde0e6bf20ef4b91bfc32ed57c0552e2e8e15463372b17a openssl-1.1.1n.tar.gz" | shasum -c && tar xaf openssl-1.1.1n.tar.gz cd openssl-1.1.1n/ ./config --prefix=/home/user/openssl --openssldir=/home/user/openssl shared zlib make @@ -108,7 +108,7 @@ For instance, by adding the following lines to `~/.bashrc` 1. Build GUI: cd zano - utils/build_sript_linux.sh + utils/build_script_linux.sh 7. Look for the binaries in `build` folder diff --git a/contrib/epee/include/misc_helpers.h b/contrib/epee/include/misc_helpers.h index fb990e8a..58ddc3cd 100644 --- a/contrib/epee/include/misc_helpers.h +++ b/contrib/epee/include/misc_helpers.h @@ -66,6 +66,7 @@ } #define CATCH_ENTRY(location, return_val) CATCH_ENTRY_CUSTOM(location, (void)0, return_val) #define CATCH_ENTRY2(return_val) CATCH_ENTRY_CUSTOM(LOCATION_SS, (void)0, return_val) +#define CATCH_ENTRY_CUSTOM2(custom_code, return_val) CATCH_ENTRY_CUSTOM(LOCATION_SS, custom_code, return_val) #define CATCH_ENTRY_L0(location, return_val) CATCH_ENTRY(location, return_val) #define CATCH_ENTRY_L1(location, return_val) CATCH_ENTRY(location, return_val) diff --git a/resources/app_icon.svg b/resources/app_icon.svg new file mode 100644 index 00000000..d5ca861a --- /dev/null +++ b/resources/app_icon.svg @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/app_icon_256.png b/resources/app_icon_256.png new file mode 100644 index 00000000..c2cb1525 Binary files /dev/null and b/resources/app_icon_256.png differ diff --git a/src/common/crypto_serialization.h b/src/common/crypto_serialization.h index 89b05d8f..a5b5bd6c 100644 --- a/src/common/crypto_serialization.h +++ b/src/common/crypto_serialization.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2022 Zano Project +// Copyright (c) 2014-2023 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 @@ -19,6 +19,8 @@ #include "crypto/hash.h" #include "crypto/range_proofs.h" #include "crypto/clsag.h" +#include "crypto/zarcanum.h" +#include "crypto/one_out_of_many_proofs.h" #include "boost_serialization_maps.h" #include "serialization/keyvalue_enable_POD_serialize_as_string.h" // @@ -94,6 +96,25 @@ namespace crypto END_BOOST_SERIALIZATION() }; + struct CLSAG_GGX_signature_serialized : public CLSAG_GGX_signature + { + BEGIN_SERIALIZE_OBJECT() + FIELD(c) + FIELD((std::vector&)(r_g)) + FIELD((std::vector&)(r_x)) + FIELD(K1) + FIELD(K2) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(c) + BOOST_SERIALIZE((std::vector&)(r_g)) + BOOST_SERIALIZE((std::vector&)(r_x)) + BOOST_SERIALIZE(K1) + BOOST_SERIALIZE(K2) + END_BOOST_SERIALIZATION() + }; + struct CLSAG_GGXG_signature_serialized : public CLSAG_GGXG_signature { BEGIN_SERIALIZE_OBJECT() @@ -115,6 +136,95 @@ namespace crypto END_BOOST_SERIALIZATION() }; + struct CLSAG_GGXXG_signature_serialized : public CLSAG_GGXXG_signature + { + BEGIN_SERIALIZE_OBJECT() + FIELD(c) + FIELD_N("r_g", (std::vector&)(r_g)) + FIELD_N("r_x", (std::vector&)(r_x)) + FIELD(K1) + FIELD(K2) + FIELD(K3) + FIELD(K4) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(c) + BOOST_SERIALIZE((std::vector&)(r_g)) + BOOST_SERIALIZE((std::vector&)(r_x)) + BOOST_SERIALIZE(K1) + BOOST_SERIALIZE(K2) + BOOST_SERIALIZE(K3) + BOOST_SERIALIZE(K4) + END_BOOST_SERIALIZATION() + }; + + struct vector_UG_aggregation_proof_serialized : public vector_UG_aggregation_proof + { + BEGIN_SERIALIZE_OBJECT() + FIELD(amount_commitments_for_rp_aggregation) + FIELD((std::vector&)(y0s)) + FIELD((std::vector&)(y1s)) + FIELD(c) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(amount_commitments_for_rp_aggregation) + BOOST_SERIALIZE((std::vector&)(y0s)) + BOOST_SERIALIZE((std::vector&)(y1s)) + BOOST_SERIALIZE(c) + END_BOOST_SERIALIZATION() + }; + + struct linear_composition_proof_s : public linear_composition_proof + { + BEGIN_SERIALIZE_OBJECT() + FIELD(c) + FIELD(y0) + FIELD(y1) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(c) + BOOST_SERIALIZE(y0) + BOOST_SERIALIZE(y1) + END_BOOST_SERIALIZATION() + }; + + struct generic_schnorr_sig_s : public generic_schnorr_sig + { + BEGIN_SERIALIZE_OBJECT() + FIELD(c) + FIELD(y) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(c) + BOOST_SERIALIZE(y) + END_BOOST_SERIALIZATION() + }; + + struct BGE_proof_s : public BGE_proof + { + BEGIN_SERIALIZE_OBJECT() + FIELD(A) + FIELD(B) + FIELD(Pk) + FIELD_N("f", (std::vector&)(f)) + FIELD(y) + FIELD(z) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(A) + BOOST_SERIALIZE(B) + BOOST_SERIALIZE(Pk) + BOOST_SERIALIZE(f) + BOOST_SERIALIZE(y) + BOOST_SERIALIZE(z) + END_BOOST_SERIALIZATION() + }; + } // namespace crypto BLOB_SERIALIZER(crypto::chacha8_iv); diff --git a/src/crypto/clsag.cpp b/src/crypto/clsag.cpp index bcc369e1..ca51c644 100644 --- a/src/crypto/clsag.cpp +++ b/src/crypto/clsag.cpp @@ -1,5 +1,5 @@ -// Copyright (c) 2022 Zano Project -// Copyright (c) 2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2022-2023 Zano Project +// Copyright (c) 2022-2023 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // @@ -13,8 +13,13 @@ DISABLE_GCC_AND_CLANG_WARNING(unused-function) namespace crypto { - #define DBG_VAL_PRINT(x) (void(0)) // std::cout << #x ": " << x << std::endl - #define DBG_PRINT(x) (void(0)) // std::cout << x << std::endl +#if 0 +# define DBG_VAL_PRINT(x) std::cout << std::setw(30) << std::left << #x ": " << x << std::endl +# define DBG_PRINT(x) std::cout << x << std::endl +#else +# define DBG_VAL_PRINT(x) (void(0)) +# define DBG_PRINT(x) (void(0)) +#endif static std::ostream &operator <<(std::ostream &o, const crypto::hash &v) { return o << pod_to_hex(v); } static std::ostream &operator <<(std::ostream &o, const crypto::public_key &v) { return o << pod_to_hex(v); } @@ -171,7 +176,327 @@ namespace crypto return c_prev == sig.c; } - + + //--------------------------------------------------------------- + + // + // Disclaimer: extensions to the CLSAG implemented below are non-standard and are in proof-of-concept state. + // They shouldn't be used in production code until formal security proofs are done and (ideally) the code is peer-reviewed. + // -- sowle + // + + + bool generate_CLSAG_GGX(const hash& m, const std::vector& ring, const point_t& pseudo_out_amount_commitment, const point_t& pseudo_out_blinded_asset_id, const key_image& ki, + const scalar_t& secret_0_xp, const scalar_t& secret_1_f, const scalar_t& secret_2_t, uint64_t secret_index, CLSAG_GGX_signature& sig) + { + DBG_PRINT("== generate_CLSAG_GGX =="); + size_t ring_size = ring.size(); + CRYPTO_CHECK_AND_THROW_MES(ring_size > 0, "ring size is zero"); + CRYPTO_CHECK_AND_THROW_MES(secret_index < ring_size, "secret_index is out of range"); + + // calculate key images + point_t ki_base = hash_helper_t::hp(ring[secret_index].stealth_address); + point_t key_image = secret_0_xp * ki_base; + +#ifndef NDEBUG + CRYPTO_CHECK_AND_THROW_MES(key_image == point_t(ki), "key image 0 mismatch"); + CRYPTO_CHECK_AND_THROW_MES((secret_0_xp * c_point_G).to_public_key() == ring[secret_index].stealth_address, "secret_0_xp mismatch"); + CRYPTO_CHECK_AND_THROW_MES( secret_1_f * c_point_G == 8 * point_t(ring[secret_index].amount_commitment) - pseudo_out_amount_commitment, "secret_1_f mismatch"); + CRYPTO_CHECK_AND_THROW_MES( secret_2_t * c_point_X == 8 * point_t(ring[secret_index].blinded_asset_id) - pseudo_out_blinded_asset_id, "secret_2_t mismatch"); + //CRYPTO_CHECK_AND_THROW_MES( secret_3_q * c_point_G == 8 * point_t(ring[secret_index].concealing_point), ""); + //CRYPTO_CHECK_AND_THROW_MES( secret_4_x * c_point_X == extended_amount_commitment - 8 * point_t(ring[secret_index].amount_commitment) - 8 * point_t(ring[secret_index].concealing_point), ""); +#endif + + point_t K1_div8 = (c_scalar_1div8 * secret_1_f) * ki_base; + K1_div8.to_public_key(sig.K1); + point_t K1 = K1_div8; + K1.modify_mul8(); + + point_t K2_div8 = (c_scalar_1div8 * secret_2_t) * ki_base; + K2_div8.to_public_key(sig.K2); + point_t K2 = K2_div8; + K2.modify_mul8(); + + //point_t K3_div8 = (c_scalar_1div8 * secret_3_q) * ki_base; + //K3_div8.to_public_key(sig.K3); + //point_t K3 = K3_div8; + //K3.modify_mul8(); + + //point_t K4_div8 = (c_scalar_1div8 * secret_4_x) * ki_base; + //K4_div8.to_public_key(sig.K4); + //point_t K4 = K4_div8; + //K4.modify_mul8(); + + // calculate aggregation coefficients + hash_helper_t::hs_t hsc(4 + 3 * ring_size); + hsc.add_scalar(m); + for(size_t i = 0; i < ring_size; ++i) + { + hsc.add_pub_key(ring[i].stealth_address); + hsc.add_pub_key(ring[i].amount_commitment); + hsc.add_pub_key(ring[i].blinded_asset_id); + DBG_PRINT("ring[" << i << "]: sa:" << ring[i].stealth_address << ", ac:" << ring[i].amount_commitment << ", baid:" << ring[i].blinded_asset_id); + } + hsc.add_point(c_scalar_1div8 * pseudo_out_amount_commitment); + hsc.add_point(c_scalar_1div8 * pseudo_out_blinded_asset_id); + hsc.add_key_image(ki); + hash input_hash = hsc.calc_hash_no_reduce(); + DBG_VAL_PRINT(input_hash); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_0); + hsc.add_hash(input_hash); + scalar_t agg_coeff_0 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_0); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_1); + hsc.add_hash(input_hash); + scalar_t agg_coeff_1 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_1); + + // may we get rid of it? + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_2); + hsc.add_hash(input_hash); + scalar_t agg_coeff_2 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_2); + + //hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_3); + //hsc.add_hash(input_hash); + //scalar_t agg_coeff_3 = hsc.calc_hash(); + //DBG_VAL_PRINT(agg_coeff_3); + + // prepare A_i, Q_i + std::vector A_i, Q_i; + A_i.reserve(ring_size), Q_i.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + A_i.emplace_back(ring[i].amount_commitment); + A_i.back().modify_mul8(); + Q_i.emplace_back(ring[i].blinded_asset_id); + Q_i.back().modify_mul8(); + DBG_PRINT("A_i[" << i << "] = " << A_i[i] << " Q_i[" << i << "] = " << Q_i[i]); + } + + // calculate aggregate pub keys (layers 0, 1; G components) + std::vector W_pub_keys_g; + W_pub_keys_g.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_g.emplace_back( + agg_coeff_0 * point_t(ring[i].stealth_address) + + agg_coeff_1 * (A_i[i] - pseudo_out_amount_commitment) + ); + DBG_VAL_PRINT(W_pub_keys_g[i]); + } + + // calculate aggregate pub keys (layer 2; X component) + std::vector W_pub_keys_x; + W_pub_keys_x.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_x.emplace_back( + agg_coeff_2 * (Q_i[i] - pseudo_out_blinded_asset_id) + ); + DBG_VAL_PRINT(W_pub_keys_x[i]); + } + + // aggregate secret key (layers 0, 1; G component) + scalar_t w_sec_key_g = agg_coeff_0 * secret_0_xp + agg_coeff_1 * secret_1_f; // + agg_coeff_3 * secret_3_q; + DBG_VAL_PRINT(w_sec_key_g * c_point_G); + + // aggregate secret key (layer 2; X component) + scalar_t w_sec_key_x = agg_coeff_2 * secret_2_t; + DBG_VAL_PRINT(w_sec_key_x * c_point_X); + + // calculate aggregate key image (layers 0, 1, 3; G component) + point_t W_key_image_g = agg_coeff_0 * key_image + agg_coeff_1 * K1 /* + agg_coeff_3 * K3 */; + DBG_VAL_PRINT(key_image); + DBG_VAL_PRINT(K1); + //DBG_VAL_PRINT(K3); + DBG_VAL_PRINT(W_key_image_g); + + // calculate aggregate key image (layer 2; X component) + point_t W_key_image_x = agg_coeff_2 * K2; + DBG_VAL_PRINT(K2); + DBG_VAL_PRINT(W_key_image_x); + +#ifndef NDEBUG + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_g * c_point_G == W_pub_keys_g[secret_index], "aggregated secret G and pub key mismatch"); + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_g * hash_helper_t::hp(ring[secret_index].stealth_address) == W_key_image_g, "aggregated secret G and key image mismatch"); + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_x * c_point_X == W_pub_keys_x[secret_index], "aggregated secret X and pub key mismatch"); + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_x * hash_helper_t::hp(ring[secret_index].stealth_address) == W_key_image_x, "aggregated secret X and key image mismatch"); +#endif + + // initial commitment + scalar_t alpha_g = scalar_t::random(); // randomness for layers 0,1 + scalar_t alpha_x = scalar_t::random(); // randomness for layer 2 + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_CHALLENGE); + hsc.add_hash(input_hash); + hsc.add_point(alpha_g * c_point_G); + hsc.add_point(alpha_g * ki_base); + hsc.add_point(alpha_x * c_point_X); + hsc.add_point(alpha_x * ki_base); + //DBG_PRINT("c[" << secret_index << "] = Hs(ih, " << alpha_g * c_point_G << ", " << alpha_g * ki_base << ", " << alpha_x * c_point_X << ", " << alpha_x * ki_base << ")"); + scalar_t c_prev = hsc.calc_hash(); // c_{secret_index + 1} + + sig.r_g.clear(); + sig.r_x.clear(); + sig.r_g.reserve(ring_size); + sig.r_x.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + sig.r_g.emplace_back(scalar_t::random()); + sig.r_x.emplace_back(scalar_t::random()); + } + + for(size_t j = 0, i = (secret_index + 1) % ring_size; j < ring_size - 1; ++j, i = (i + 1) % ring_size) + { + DBG_PRINT("c[" << i << "] = " << c_prev); + if (i == 0) + sig.c = c_prev; // c_0 + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_CHALLENGE); + hsc.add_hash(input_hash); + hsc.add_point(sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i]); + hsc.add_point(sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g); + hsc.add_point(sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i]); + hsc.add_point(sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x); + c_prev = hsc.calc_hash(); // c_{i + 1} + } + DBG_PRINT("c[" << secret_index << "] = " << c_prev); + + if (secret_index == 0) + sig.c = c_prev; + + sig.r_g[secret_index] = alpha_g - c_prev * w_sec_key_g; + sig.r_x[secret_index] = alpha_x - c_prev * w_sec_key_x; + + return true; + } + + bool verify_CLSAG_GGX(const hash& m, const std::vector& ring, const public_key& pseudo_out_amount_commitment, + const public_key& pseudo_out_blinded_asset_id, const key_image& ki, const CLSAG_GGX_signature& sig) + { + DBG_PRINT("== verify_CLSAG_GGX =="); + size_t ring_size = ring.size(); + CRYPTO_CHECK_AND_THROW_MES(ring_size > 0, "ring size is zero"); + CRYPTO_CHECK_AND_THROW_MES(ring_size == sig.r_g.size(), "ring size != r_g size"); + CRYPTO_CHECK_AND_THROW_MES(ring_size == sig.r_x.size(), "ring size != r_x size"); + + point_t key_image(ki); + CRYPTO_CHECK_AND_THROW_MES(key_image.is_in_main_subgroup(), "key image 0 does not belong to the main subgroup"); + + point_t pseudo_out_amount_commitment_pt(pseudo_out_amount_commitment); + pseudo_out_amount_commitment_pt.modify_mul8(); + + point_t pseudo_out_blinded_asset_id_pt(pseudo_out_blinded_asset_id); + pseudo_out_blinded_asset_id_pt.modify_mul8(); + + // calculate aggregation coefficients + hash_helper_t::hs_t hsc(4 + 3 * ring_size); + hsc.add_scalar(m); + for(size_t i = 0; i < ring_size; ++i) + { + hsc.add_pub_key(ring[i].stealth_address); + hsc.add_pub_key(ring[i].amount_commitment); + hsc.add_pub_key(ring[i].blinded_asset_id); + DBG_PRINT("ring[" << i << "]: sa:" << ring[i].stealth_address << ", ac:" << ring[i].amount_commitment << ", baid:" << ring[i].blinded_asset_id); + } + hsc.add_pub_key(pseudo_out_amount_commitment); + hsc.add_pub_key(pseudo_out_blinded_asset_id); + hsc.add_key_image(ki); + hash input_hash = hsc.calc_hash_no_reduce(); + DBG_VAL_PRINT(input_hash); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_0); + hsc.add_hash(input_hash); + scalar_t agg_coeff_0 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_0); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_1); + hsc.add_hash(input_hash); + scalar_t agg_coeff_1 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_1); + + // may we get rid of it? + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_2); + hsc.add_hash(input_hash); + scalar_t agg_coeff_2 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_2); + + //hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_LAYER_3); + //hsc.add_hash(input_hash); + //scalar_t agg_coeff_3 = hsc.calc_hash(); + //DBG_VAL_PRINT(agg_coeff_3); + + // prepare A_i, Q_i + std::vector A_i, Q_i; + A_i.reserve(ring_size), Q_i.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + A_i.emplace_back(ring[i].amount_commitment); + A_i.back().modify_mul8(); + Q_i.emplace_back(ring[i].blinded_asset_id); + Q_i.back().modify_mul8(); + DBG_PRINT("A_i[" << i << "] = " << A_i[i] << " Q_i[" << i << "] = " << Q_i[i]); + } + + // calculate aggregate pub keys (layers 0, 1; G components) + std::vector W_pub_keys_g; + W_pub_keys_g.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_g.emplace_back( + agg_coeff_0 * point_t(ring[i].stealth_address) + + agg_coeff_1 * (A_i[i] - pseudo_out_amount_commitment_pt) + ); + DBG_VAL_PRINT(W_pub_keys_g[i]); + } + + // calculate aggregate pub keys (layer 2; X component) + std::vector W_pub_keys_x; + W_pub_keys_x.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_x.emplace_back( + agg_coeff_2 * (Q_i[i] - pseudo_out_blinded_asset_id_pt) + ); + DBG_VAL_PRINT(W_pub_keys_x[i]); + } + + // calculate aggregate key image (layers 0, 1, 3; G components) + point_t W_key_image_g = + agg_coeff_0 * key_image + + agg_coeff_1 * point_t(sig.K1).modify_mul8(); + // agg_coeff_3 * point_t(sig.K3).modify_mul8(); + DBG_VAL_PRINT(point_t(sig.K1).modify_mul8()); + //DBG_VAL_PRINT(point_t(sig.K3).modify_mul8()); + DBG_VAL_PRINT(W_key_image_g); + + // calculate aggregate key image (layer 2; X component) + point_t W_key_image_x = + agg_coeff_2 * point_t(sig.K2).modify_mul8(); + DBG_VAL_PRINT(point_t(sig.K2).modify_mul8()); + DBG_VAL_PRINT(W_key_image_x); + + + scalar_t c_prev = sig.c; + DBG_PRINT("c[0] = " << c_prev); + for(size_t i = 0; i < ring_size; ++i) + { + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGX_CHALLENGE); + hsc.add_hash(input_hash); + hsc.add_point(sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i]); + hsc.add_point(sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g); + hsc.add_point(sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i]); + hsc.add_point(sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x); + c_prev = hsc.calc_hash(); // c_{i + 1} + DBG_PRINT("c[" << i + 1 << "] = " << c_prev); + //DBG_PRINT("c[" << i + 1 << "] = Hs(ih, " << sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i] << ", " << sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g << ", " << sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i] << ", " << sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x << ")"); + } + + return c_prev == sig.c; + } + + //--------------------------------------------------------------- @@ -192,7 +517,7 @@ namespace crypto CRYPTO_CHECK_AND_THROW_MES((secret_0_xp * c_point_G).to_public_key() == ring[secret_index].stealth_address, "secret_0_xp mismatch"); CRYPTO_CHECK_AND_THROW_MES(secret_1_f * c_point_G == 8 * point_t(ring[secret_index].amount_commitment) - pseudo_out_amount_commitment, "secret_1_f mismatch"); CRYPTO_CHECK_AND_THROW_MES(secret_3_q * c_point_G == 8 * point_t(ring[secret_index].concealing_point), "secret_3_q mismatch"); - CRYPTO_CHECK_AND_THROW_MES(secret_2_x * c_point_X == extended_amount_commitment - 8 * point_t(ring[secret_index].amount_commitment) - 8 * point_t(ring[secret_index].concealing_point), "secret_3_q mismatch"); + CRYPTO_CHECK_AND_THROW_MES(secret_2_x * c_point_X == extended_amount_commitment - 8 * point_t(ring[secret_index].amount_commitment) - 8 * point_t(ring[secret_index].concealing_point), "secret_2_x mismatch"); #endif point_t K1_div8 = (c_scalar_1div8 * secret_1_f) * ki_base; @@ -482,4 +807,354 @@ namespace crypto return c_prev == sig.c; } + + //--------------------------------------------------------------- + + + bool generate_CLSAG_GGXXG(const hash& m, const std::vector& ring, const point_t& pseudo_out_amount_commitment, const point_t& pseudo_out_blinded_asset_id, const point_t& extended_amount_commitment, const key_image& ki, + const scalar_t& secret_0_xp, const scalar_t& secret_1_f, const scalar_t& secret_2_t, const scalar_t& secret_3_x, const scalar_t& secret_4_q, uint64_t secret_index, + CLSAG_GGXXG_signature& sig) + { + DBG_PRINT("== generate_CLSAG_GGXXG =="); + size_t ring_size = ring.size(); + CRYPTO_CHECK_AND_THROW_MES(ring_size > 0, "ring size is zero"); + CRYPTO_CHECK_AND_THROW_MES(secret_index < ring_size, "secret_index is out of range"); + + // calculate key images + point_t ki_base = hash_helper_t::hp(ring[secret_index].stealth_address); + point_t key_image = secret_0_xp * ki_base; + +#ifndef NDEBUG + CRYPTO_CHECK_AND_THROW_MES(key_image == point_t(ki), "key image 0 mismatch"); + CRYPTO_CHECK_AND_THROW_MES((secret_0_xp * c_point_G).to_public_key() == ring[secret_index].stealth_address, "secret_0_xp mismatch"); + CRYPTO_CHECK_AND_THROW_MES( secret_1_f * c_point_G == 8 * point_t(ring[secret_index].amount_commitment) - pseudo_out_amount_commitment, "secret_1_f mismatch"); + CRYPTO_CHECK_AND_THROW_MES( secret_2_t * c_point_X == 8 * point_t(ring[secret_index].blinded_asset_id) - pseudo_out_blinded_asset_id, "secret_2_t mismatch"); + CRYPTO_CHECK_AND_THROW_MES( secret_3_x * c_point_X == extended_amount_commitment - 8 * point_t(ring[secret_index].amount_commitment) - 8 * point_t(ring[secret_index].concealing_point), ""); + CRYPTO_CHECK_AND_THROW_MES( secret_4_q * c_point_G == 8 * point_t(ring[secret_index].concealing_point), "secret_3_q mismatch"); +#endif + + point_t K1_div8 = (c_scalar_1div8 * secret_1_f) * ki_base; + K1_div8.to_public_key(sig.K1); + point_t K1 = K1_div8; + K1.modify_mul8(); + + point_t K2_div8 = (c_scalar_1div8 * secret_2_t) * ki_base; + K2_div8.to_public_key(sig.K2); + point_t K2 = K2_div8; + K2.modify_mul8(); + + point_t K3_div8 = (c_scalar_1div8 * secret_3_x) * ki_base; + K3_div8.to_public_key(sig.K3); + point_t K3 = K3_div8; + K3.modify_mul8(); + + point_t K4_div8 = (c_scalar_1div8 * secret_4_q) * ki_base; + K4_div8.to_public_key(sig.K4); + point_t K4 = K4_div8; + K4.modify_mul8(); + + // calculate aggregation coefficients + hash_helper_t::hs_t hsc(5 + 4 * ring_size); + hsc.add_scalar(m); + for(size_t i = 0; i < ring_size; ++i) + { + hsc.add_pub_key(ring[i].stealth_address); + hsc.add_pub_key(ring[i].amount_commitment); + hsc.add_pub_key(ring[i].blinded_asset_id); + hsc.add_pub_key(ring[i].concealing_point); + DBG_PRINT("ring[" << i << "]: sa:" << ring[i].stealth_address << ", ac:" << ring[i].amount_commitment << ", baid:" << ring[i].blinded_asset_id << ", cp:" << ring[i].concealing_point); + } + hsc.add_point(c_scalar_1div8 * pseudo_out_amount_commitment); + hsc.add_point(c_scalar_1div8 * pseudo_out_blinded_asset_id); + hsc.add_point(c_scalar_1div8 * extended_amount_commitment); + hsc.add_key_image(ki); + hash input_hash = hsc.calc_hash_no_reduce(); + DBG_VAL_PRINT(input_hash); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_0); + hsc.add_hash(input_hash); + scalar_t agg_coeff_0 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_0); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_1); + hsc.add_hash(input_hash); + scalar_t agg_coeff_1 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_1); + + // may we get rid of it? + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_2); + hsc.add_hash(input_hash); + scalar_t agg_coeff_2 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_2); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_3); + hsc.add_hash(input_hash); + scalar_t agg_coeff_3 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_3); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_4); + hsc.add_hash(input_hash); + scalar_t agg_coeff_4 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_4); + + // prepare A_i, Q_i + std::vector A_i, P_i, Q_i; + A_i.reserve(ring_size), P_i.reserve(ring_size), Q_i.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + A_i.emplace_back(ring[i].amount_commitment); + A_i.back().modify_mul8(); + P_i.emplace_back(ring[i].blinded_asset_id); + P_i.back().modify_mul8(); + Q_i.emplace_back(ring[i].concealing_point); + Q_i.back().modify_mul8(); + DBG_PRINT("A_i[" << i << "] = " << A_i[i] << " P_i[" << i << "] = " << P_i[i] << " Q_i[" << i << "] = " << Q_i[i]); + } + + // calculate aggregate pub keys (layers 0, 1, 4; G components) + std::vector W_pub_keys_g; + W_pub_keys_g.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_g.emplace_back( + agg_coeff_0 * point_t(ring[i].stealth_address) + + agg_coeff_1 * (A_i[i] - pseudo_out_amount_commitment) + + agg_coeff_4 * Q_i[i] + ); + DBG_VAL_PRINT(W_pub_keys_g[i]); + } + + // calculate aggregate pub keys (layerx 2, 3; X component) + std::vector W_pub_keys_x; + W_pub_keys_x.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_x.emplace_back( + agg_coeff_2 * (P_i[i] - pseudo_out_blinded_asset_id) + + agg_coeff_3 * (extended_amount_commitment - A_i[i] - Q_i[i]) + ); + DBG_VAL_PRINT(W_pub_keys_x[i]); + } + + // aggregate secret key (layers 0, 1, 4; G component) + scalar_t w_sec_key_g = agg_coeff_0 * secret_0_xp + agg_coeff_1 * secret_1_f + agg_coeff_4 * secret_4_q; + DBG_VAL_PRINT(w_sec_key_g * c_point_G); + + // aggregate secret key (layer 2, 3; X component) + scalar_t w_sec_key_x = agg_coeff_2 * secret_2_t + agg_coeff_3 * secret_3_x; + DBG_VAL_PRINT(w_sec_key_x * c_point_X); + + // calculate aggregate key image (layers 0, 1, 4; G component) + point_t W_key_image_g = agg_coeff_0 * key_image + agg_coeff_1 * K1 + agg_coeff_4 * K4; + DBG_VAL_PRINT(key_image); + DBG_VAL_PRINT(K1); + DBG_VAL_PRINT(K4); + DBG_VAL_PRINT(W_key_image_g); + + // calculate aggregate key image (layer 2, 3; X component) + point_t W_key_image_x = agg_coeff_2 * K2 + agg_coeff_3 * K3; + DBG_VAL_PRINT(K2); + DBG_VAL_PRINT(K3); + DBG_VAL_PRINT(W_key_image_x); + +#ifndef NDEBUG + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_g * c_point_G == W_pub_keys_g[secret_index], "aggregated secret G and pub key mismatch"); + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_g * hash_helper_t::hp(ring[secret_index].stealth_address) == W_key_image_g, "aggregated secret G and key image mismatch"); + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_x * c_point_X == W_pub_keys_x[secret_index], "aggregated secret X and pub key mismatch"); + CRYPTO_CHECK_AND_THROW_MES(w_sec_key_x * hash_helper_t::hp(ring[secret_index].stealth_address) == W_key_image_x, "aggregated secret X and key image mismatch"); +#endif + + // initial commitment + scalar_t alpha_g = scalar_t::random(); // randomness for layers 0,1,4 + scalar_t alpha_x = scalar_t::random(); // randomness for layer 2,3 + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_CHALLENGE); + hsc.add_hash(input_hash); + hsc.add_point(alpha_g * c_point_G); + hsc.add_point(alpha_g * ki_base); + hsc.add_point(alpha_x * c_point_X); + hsc.add_point(alpha_x * ki_base); + //DBG_PRINT("c[" << secret_index << "] = Hs(ih, " << alpha_g * c_point_G << ", " << alpha_g * ki_base << ", " << alpha_x * c_point_X << ", " << alpha_x * ki_base << ")"); + scalar_t c_prev = hsc.calc_hash(); // c_{secret_index + 1} + + sig.r_g.clear(); + sig.r_x.clear(); + sig.r_g.reserve(ring_size); + sig.r_x.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + sig.r_g.emplace_back(scalar_t::random()); + sig.r_x.emplace_back(scalar_t::random()); + } + + for(size_t j = 0, i = (secret_index + 1) % ring_size; j < ring_size - 1; ++j, i = (i + 1) % ring_size) + { + DBG_PRINT("c[" << i << "] = " << c_prev); + if (i == 0) + sig.c = c_prev; // c_0 + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_CHALLENGE); + hsc.add_hash(input_hash); + hsc.add_point(sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i]); + hsc.add_point(sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g); + hsc.add_point(sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i]); + hsc.add_point(sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x); + c_prev = hsc.calc_hash(); // c_{i + 1} + //DBG_PRINT("c[" << i + 1 << "] = Hs(ih, " << sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i] << ", " << sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g << ", " << sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i] << ", " << sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x << ")"); + } + DBG_PRINT("c[" << secret_index << "] = " << c_prev); + + + if (secret_index == 0) + sig.c = c_prev; + + sig.r_g[secret_index] = alpha_g - c_prev * w_sec_key_g; + sig.r_x[secret_index] = alpha_x - c_prev * w_sec_key_x; + + return true; + } + + bool verify_CLSAG_GGXXG(const hash& m, const std::vector& ring, const public_key& pseudo_out_amount_commitment, const public_key& pseudo_out_blinded_asset_id, const public_key& extended_amount_commitment, const key_image& ki, + const CLSAG_GGXXG_signature& sig) + { + DBG_PRINT("== verify_CLSAG_GGXXG =="); + size_t ring_size = ring.size(); + CRYPTO_CHECK_AND_THROW_MES(ring_size > 0, "ring size is zero"); + CRYPTO_CHECK_AND_THROW_MES(ring_size == sig.r_g.size(), "ring size != r_g size"); + CRYPTO_CHECK_AND_THROW_MES(ring_size == sig.r_x.size(), "ring size != r_x size"); + + point_t key_image(ki); + CRYPTO_CHECK_AND_THROW_MES(key_image.is_in_main_subgroup(), "key image 0 does not belong to the main subgroup"); + + point_t pseudo_out_amount_commitment_pt(pseudo_out_amount_commitment); + pseudo_out_amount_commitment_pt.modify_mul8(); + + point_t pseudo_out_blinded_asset_id_pt(pseudo_out_blinded_asset_id); + pseudo_out_blinded_asset_id_pt.modify_mul8(); + + point_t extended_amount_commitment_pt(extended_amount_commitment); + extended_amount_commitment_pt.modify_mul8(); + + // calculate aggregation coefficients + hash_helper_t::hs_t hsc(5 + 4 * ring_size); + hsc.add_scalar(m); + for(size_t i = 0; i < ring_size; ++i) + { + hsc.add_pub_key(ring[i].stealth_address); + hsc.add_pub_key(ring[i].amount_commitment); + hsc.add_pub_key(ring[i].blinded_asset_id); + hsc.add_pub_key(ring[i].concealing_point); + DBG_PRINT("ring[" << i << "]: sa:" << ring[i].stealth_address << ", ac:" << ring[i].amount_commitment << ", baid:" << ring[i].blinded_asset_id << ", cp:" << ring[i].concealing_point); + } + hsc.add_pub_key(pseudo_out_amount_commitment); + hsc.add_pub_key(pseudo_out_blinded_asset_id); + hsc.add_pub_key(extended_amount_commitment); + hsc.add_key_image(ki); + hash input_hash = hsc.calc_hash_no_reduce(); + DBG_VAL_PRINT(input_hash); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_0); + hsc.add_hash(input_hash); + scalar_t agg_coeff_0 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_0); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_1); + hsc.add_hash(input_hash); + scalar_t agg_coeff_1 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_1); + + // may we get rid of it? + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_2); + hsc.add_hash(input_hash); + scalar_t agg_coeff_2 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_2); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_3); + hsc.add_hash(input_hash); + scalar_t agg_coeff_3 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_3); + + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_LAYER_4); + hsc.add_hash(input_hash); + scalar_t agg_coeff_4 = hsc.calc_hash(); + DBG_VAL_PRINT(agg_coeff_4); + + // prepare A_i, Q_i + std::vector A_i, P_i, Q_i; + A_i.reserve(ring_size), P_i.reserve(ring_size), Q_i.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + A_i.emplace_back(ring[i].amount_commitment); + A_i.back().modify_mul8(); + P_i.emplace_back(ring[i].blinded_asset_id); + P_i.back().modify_mul8(); + Q_i.emplace_back(ring[i].concealing_point); + Q_i.back().modify_mul8(); + DBG_PRINT("A_i[" << i << "] = " << A_i[i] << " P_i[" << i << "] = " << P_i[i] << " Q_i[" << i << "] = " << Q_i[i]); + } + + // calculate aggregate pub keys (layers 0, 1, 4; G components) + std::vector W_pub_keys_g; + W_pub_keys_g.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_g.emplace_back( + agg_coeff_0 * point_t(ring[i].stealth_address) + + agg_coeff_1 * (A_i[i] - pseudo_out_amount_commitment_pt) + + agg_coeff_4 * Q_i[i] + ); + DBG_VAL_PRINT(W_pub_keys_g[i]); + } + + // calculate aggregate pub keys (layer 2, 3; X component) + std::vector W_pub_keys_x; + W_pub_keys_x.reserve(ring_size); + for(size_t i = 0; i < ring_size; ++i) + { + W_pub_keys_x.emplace_back( + agg_coeff_2 * (P_i[i] - pseudo_out_blinded_asset_id_pt) + + agg_coeff_3 * (extended_amount_commitment_pt - A_i[i] - Q_i[i]) + ); + DBG_VAL_PRINT(W_pub_keys_x[i]); + } + + DBG_VAL_PRINT(point_t(ki)); + + // calculate aggregate key image (layers 0, 1, 4; G components) + DBG_VAL_PRINT(point_t(sig.K1).modify_mul8()); + point_t W_key_image_g = + agg_coeff_0 * key_image + + agg_coeff_1 * point_t(sig.K1).modify_mul8() + + agg_coeff_4 * point_t(sig.K4).modify_mul8(); + DBG_VAL_PRINT(point_t(sig.K1).modify_mul8()); + DBG_VAL_PRINT(point_t(sig.K4).modify_mul8()); + DBG_VAL_PRINT(W_key_image_g); + + // calculate aggregate key image (layer 2, 3; X component) + point_t W_key_image_x = + agg_coeff_2 * point_t(sig.K2).modify_mul8() + + agg_coeff_3 * point_t(sig.K3).modify_mul8(); + DBG_VAL_PRINT(point_t(sig.K2).modify_mul8()); + DBG_VAL_PRINT(point_t(sig.K3).modify_mul8()); + DBG_VAL_PRINT(W_key_image_x); + + + scalar_t c_prev = sig.c; + DBG_PRINT("c[0] = " << c_prev); + for(size_t i = 0; i < ring_size; ++i) + { + hsc.add_32_chars(CRYPTO_HDS_CLSAG_GGXXG_CHALLENGE); + hsc.add_hash(input_hash); + hsc.add_point(sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i]); + hsc.add_point(sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g); + hsc.add_point(sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i]); + hsc.add_point(sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x); + c_prev = hsc.calc_hash(); // c_{i + 1} + DBG_PRINT("c[" << i + 1 << "] = " << c_prev); + //DBG_PRINT("c[" << i + 1 << "] = Hs(ih, " << sig.r_g[i] * c_point_G + c_prev * W_pub_keys_g[i] << ", " << sig.r_g[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_g << ", " << sig.r_x[i] * c_point_X + c_prev * W_pub_keys_x[i] << ", " << sig.r_x[i] * hash_helper_t::hp(ring[i].stealth_address) + c_prev * W_key_image_x << ")"); + } + + return c_prev == sig.c; + } + + + } // namespace crypto diff --git a/src/crypto/clsag.h b/src/crypto/clsag.h index 8a4400e5..576ba917 100644 --- a/src/crypto/clsag.h +++ b/src/crypto/clsag.h @@ -1,5 +1,5 @@ -// Copyright (c) 2022 Zano Project -// Copyright (c) 2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2022-2023 Zano Project +// Copyright (c) 2022-2023 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // @@ -47,7 +47,52 @@ namespace crypto bool verify_CLSAG_GG(const hash& m, const std::vector& ring, const public_key& pseudo_out_amount_commitment, const key_image& ki, const CLSAG_GG_signature& sig); - + + // + // Disclaimer: extensions to the CLSAG implemented below are non-standard and are in proof-of-concept state. + // They shouldn't be used in production code until formal security proofs are done and (ideally) the code is peer-reviewed. + // -- sowle + // + + + + // + // 3-CLSAG + // + + + // 3-CLSAG signature (with respect to the group element G, G, X -- that's why 'GGX') + struct CLSAG_GGX_signature + { + scalar_t c; + scalar_vec_t r_g; // for G-components (layers 0, 1), size = size of the ring + scalar_vec_t r_x; // for X-component (layer 2), size = size of the ring + public_key K1; // auxiliary key image for layer 1 (G) + public_key K2; // auxiliary key image for layer 2 (X) + }; + + struct CLSAG_GGX_input_ref_t : public CLSAG_GG_input_ref_t + { + CLSAG_GGX_input_ref_t(const public_key& stealth_address, const public_key& amount_commitment, const public_key& blinded_asset_id) + : CLSAG_GG_input_ref_t(stealth_address, amount_commitment) + , blinded_asset_id(blinded_asset_id) + {} + + const public_key& blinded_asset_id; // T, premultiplied by 1/8 + }; + + // pseudo_out_amount_commitment -- not premultiplied by 1/8 + // pseudo_out_asset_id -- not premultiplied by 1/8 + bool generate_CLSAG_GGX(const hash& m, const std::vector& ring, const point_t& pseudo_out_amount_commitment, const point_t& pseudo_out_asset_id, const key_image& ki, + const scalar_t& secret_0_xp, const scalar_t& secret_1_f, const scalar_t& secret_2_t, uint64_t secret_index, CLSAG_GGX_signature& sig); + + // pseudo_out_amount_commitment -- premultiplied by 1/8 + // pseudo_out_asset_id -- premultiplied by 1/8 + // may throw an exception TODO @#@# make sure it's okay + bool verify_CLSAG_GGX(const hash& m, const std::vector& ring, const public_key& pseudo_out_amount_commitment, + const public_key& pseudo_out_asset_id, const key_image& ki, const CLSAG_GGX_signature& sig); + + // // 4-CLSAG // @@ -85,4 +130,47 @@ namespace crypto bool verify_CLSAG_GGXG(const hash& m, const std::vector& ring, const public_key& pseudo_out_amount_commitment, const public_key& extended_amount_commitment, const key_image& ki, const CLSAG_GGXG_signature& sig); + + + // + // 5-CLSAG + // + + + // 5-CLSAG signature (with respect to the group element G, G, X, X, G -- that's why 'GGXXG') + struct CLSAG_GGXXG_signature + { + scalar_t c; + scalar_vec_t r_g; // for G-components (layers 0, 1, 4), size = size of the ring + scalar_vec_t r_x; // for X-component (layers 2, 3), size = size of the ring + public_key K1; // auxiliary key image for layer 1 (G) + public_key K2; // auxiliary key image for layer 2 (X) + public_key K3; // auxiliary key image for layer 2 (X) + public_key K4; // auxiliary key image for layer 3 (G) + }; + + struct CLSAG_GGXXG_input_ref_t : public CLSAG_GGX_input_ref_t + { + CLSAG_GGXXG_input_ref_t(const public_key& stealth_address, const public_key& amount_commitment, const public_key& blinded_asset_id, const public_key& concealing_point) + : CLSAG_GGX_input_ref_t(stealth_address, amount_commitment, blinded_asset_id) + , concealing_point(concealing_point) + {} + + const public_key& concealing_point; // Q, premultiplied by 1/8 + }; + + // pseudo_out_amount_commitment -- not premultiplied by 1/8 + // pseudo_out_asset_id -- not premultiplied by 1/8 + // extended_amount_commitment -- not premultiplied by 1/8 + bool generate_CLSAG_GGXXG(const hash& m, const std::vector& ring, const point_t& pseudo_out_amount_commitment, const point_t& pseudo_out_blinded_asset_id, const point_t& extended_amount_commitment, const key_image& ki, + const scalar_t& secret_0_xp, const scalar_t& secret_1_f, const scalar_t& secret_2_r, const scalar_t& secret_3_x, const scalar_t& secret_4_q, uint64_t secret_index, CLSAG_GGXXG_signature& sig); + + // pseudo_out_amount_commitment -- premultiplied by 1/8 + // pseudo_out_asset_id -- premultiplied by 1/8 + // extended_amount_commitment -- premultiplied by 1/8 + // may throw an exception TODO @#@# make sure it's okay + bool verify_CLSAG_GGXXG(const hash& m, const std::vector& ring, const public_key& pseudo_out_amount_commitment, const public_key& pseudo_out_blinded_asset_id, const public_key& extended_amount_commitment, const key_image& ki, + const CLSAG_GGXXG_signature& sig); + + } // namespace crypto diff --git a/src/crypto/crypto-sugar.cpp b/src/crypto/crypto-sugar.cpp index 1823833a..fe7cd63a 100644 --- a/src/crypto/crypto-sugar.cpp +++ b/src/crypto/crypto-sugar.cpp @@ -10,27 +10,5 @@ namespace crypto { - const point_g_t c_point_G; - - const scalar_t c_scalar_1 = { 1 }; - const scalar_t c_scalar_2p64 = { 0, 1, 0, 0 }; - const scalar_t c_scalar_L = { 0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; - const scalar_t c_scalar_Lm1 = { 0x5812631a5cf5d3ec, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; - const scalar_t c_scalar_P = { 0xffffffffffffffed, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; - const scalar_t c_scalar_Pm1 = { 0xffffffffffffffec, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; - const scalar_t c_scalar_256m1 = { 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff }; - const scalar_t c_scalar_1div8 = { 0x6106e529e2dc2f79, 0x07d39db37d1cdad0, 0x0, 0x0600000000000000 }; - - const point_t c_point_H = { 0x05087c1f5b9b32d6, 0x00547595f445c3b5, 0x764df64578552f2a, 0x8a49a651e0e0da45 }; // == Hp(G), this is being checked in bpp_basics - const point_t c_point_H2 = { 0x70c8d1ab9dbf1cc0, 0xc561bb12639a8516, 0x3cfff1def9e5b268, 0xe0936386f3bcce1a }; // == Hp("h2_generator"), checked in bpp_basics - - const point_t c_point_X = { 0xc9d2f543dbbc253a, 0x87099e9ac33d06dd, 0x76bcf12dcf6ffcba, 0x20384a4a88752d32 }; // == Hp("X_generator"), checked in clsag_ggxg_basics - - const point_t c_point_0 = point_t(point_t::tag_zero()); - - const point_t c_point_H_plus_G = c_point_H + c_point_G; // checked in crypto_constants - const point_t c_point_H_minus_G = c_point_H - c_point_G; // checked in crypto_constants - - static_assert(sizeof(scalar_t::m_sk) == sizeof(scalar_t::m_u64) && sizeof(scalar_t::m_u64) == sizeof(scalar_t::m_s), "size missmatch"); } // namespace crypto diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 7ebe078e..643ca19b 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -1,5 +1,5 @@ -// Copyright (c) 2020-2022 Zano Project -// Copyright (c) 2020-2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2020-2023 Zano Project +// Copyright (c) 2020-2023 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // @@ -20,6 +20,34 @@ namespace crypto // Helpers // + // returns greatest k, s.t. n**k <= v + // tests in crypto_tests_range_proofs.h + constexpr uint64_t constexpr_floor_log_n(uint64_t v, uint64_t n) + { + return (v < n || n <= 1) ? 0 : constexpr_floor_log_n(v / n, n) + 1; + } + + // returns smallest k, s.t. v <= n**k + // tests in crypto_tests_range_proofs.h + constexpr uint64_t constexpr_ceil_log_n(uint64_t v, uint64_t n) + { + return (v <= 1 || n <= 1) ? 0 : constexpr_floor_log_n(v - 1, n) + 1; + } + + // returns smallest k, s.t. v <= 2**k + // tests in crypto_tests_range_proofs.h + constexpr uint64_t constexpr_ceil_log2(uint64_t v) + { + return constexpr_ceil_log_n(v, 2); + } + + // returns base ** k + constexpr uint64_t constexpr_pow(uint64_t k, uint64_t base) + { + return k == 0 ? 1 : base * constexpr_pow(k - 1, base); + } + + template std::string pod_to_hex_reversed(const pod_t &h) { @@ -70,6 +98,22 @@ namespace crypto return ss.str(); } + template + std::string pod_to_comma_separated_chars(const pod_t &h) + { + std::stringstream ss; + ss << std::hex << std::setfill('0'); + size_t len = sizeof h; + const unsigned char* p = (const unsigned char*)&h; + for (size_t i = 0; i < len; ++i) + { + ss << "'\\x" << std::setw(2) << static_cast(p[i]) << "'"; + if (i + 1 != len) + ss << ", "; + } + return ss.str(); + } + template std::string pod_to_hex_comma_separated_uint64(const pod_t &h) { @@ -87,6 +131,22 @@ namespace crypto return ss.str(); } + template + std::string pod_to_comma_separated_int32(const pod_t &h) + { + static_assert((sizeof h) % 4 == 0, "size of h should be a multiple of 32 bit"); + size_t len = (sizeof h) / 4; + std::stringstream ss; + const int32_t* p = (const int32_t*)&h; + for (size_t i = 0; i < len; ++i) + { + ss << static_cast(p[i]); + if (i + 1 != len) + ss << ", "; + } + return ss.str(); + } + template bool parse_tpod_from_hex_string(const std::string& hex_str, t_pod_type& t_pod) { @@ -144,12 +204,9 @@ namespace crypto scalar_t() = default; // won't check scalar range validity (< L) - scalar_t(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3) + constexpr scalar_t(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3) noexcept + : m_u64{a0, a1, a2, a3} { - m_u64[0] = a0; - m_u64[1] = a1; - m_u64[2] = a2; - m_u64[3] = a3; } // won't check scalar range validity (< L) @@ -309,6 +366,14 @@ namespace crypto return *this; } + scalar_t operator-() const + { + static unsigned char zero[32] = { 0 }; + scalar_t result; + sc_sub(&result.m_s[0], zero, &m_s[0]); + return result; + } + // returns this = a * b scalar_t& assign_mul(const scalar_t& a, const scalar_t& b) { @@ -319,7 +384,7 @@ namespace crypto /* I think it has bad symantic (operator-like), consider rename/reimplement -- sowle */ - // returns this * b + c + // returns c + this * b scalar_t muladd(const scalar_t& b, const scalar_t& c) const { scalar_t result; @@ -327,13 +392,20 @@ namespace crypto return result; } - // returns this = a * b + c + // returns this = c + a * b scalar_t& assign_muladd(const scalar_t& a, const scalar_t& b, const scalar_t& c) { sc_muladd(m_s, a.m_s, b.m_s, c.m_s); return *this; } + // returns this = c - a * b + scalar_t& assign_mulsub(const scalar_t& a, const scalar_t& b, const scalar_t& c) + { + sc_mulsub(m_s, a.m_s, b.m_s, c.m_s); + return *this; + } + scalar_t reciprocal() const { scalar_t result; @@ -443,6 +515,7 @@ namespace crypto m_u64[bit_index >> 6] &= ~(1ull << (bit_index & 63)); } + // does not reduce static scalar_t power_of_2(uint8_t exponent) { scalar_t result = 0; @@ -453,17 +526,20 @@ namespace crypto }; // struct scalar_t // - // Global constants + // Global constants (checked in crypto_constants) // - extern const scalar_t c_scalar_1; - extern const scalar_t c_scalar_2p64; - extern const scalar_t c_scalar_L; - extern const scalar_t c_scalar_Lm1; - extern const scalar_t c_scalar_P; - extern const scalar_t c_scalar_Pm1; - extern const scalar_t c_scalar_256m1; - extern const scalar_t c_scalar_1div8; + static constexpr scalar_t c_scalar_0 = { 0, 0, 0, 0 }; + static constexpr scalar_t c_scalar_1 = { 1, 0, 0, 0 }; + static constexpr scalar_t c_scalar_2p64 = { 0, 1, 0, 0 }; + static constexpr scalar_t c_scalar_L = { 0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; + static constexpr scalar_t c_scalar_Lm1 = { 0x5812631a5cf5d3ec, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; + static constexpr scalar_t c_scalar_P = { 0xffffffffffffffed, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; + static constexpr scalar_t c_scalar_Pm1 = { 0xffffffffffffffec, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; + static constexpr scalar_t c_scalar_256m1 = { 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff }; + static constexpr scalar_t c_scalar_1div8 = { 0x6106e529e2dc2f79, 0x07d39db37d1cdad0, 0x0, 0x0600000000000000 }; + + static_assert(sizeof(scalar_t::m_sk) == sizeof(scalar_t::m_u64) && sizeof(scalar_t::m_u64) == sizeof(scalar_t::m_s), "size missmatch"); // // @@ -476,9 +552,7 @@ namespace crypto // with x = X / Z, y = Y / Z, x * y = T / Z. ge_p3 m_p3; - point_t() - { - } + point_t() = default; explicit point_t(const crypto::public_key& pk) // can throw std::runtime_error { @@ -518,6 +592,16 @@ namespace crypto { } + explicit constexpr point_t(const int32_t(&v)[40]) noexcept + : m_p3{ + {v[ 0], v[ 1], v[ 2], v[ 3], v[ 4], v[ 5], v[ 6], v[ 7], v[ 8], v[9]}, + {v[10], v[11], v[12], v[13], v[14], v[15], v[16], v[17], v[18], v[19]}, + {v[20], v[21], v[22], v[23], v[24], v[25], v[26], v[27], v[28], v[29]}, + {v[30], v[31], v[32], v[33], v[34], v[35], v[36], v[37], v[38], v[39]} + } + { + } + // as we're using additive notation, zero means identity group element (EC point (0, 1)) here and after void zero() { @@ -694,12 +778,32 @@ namespace crypto return false; return true; - }; + } friend bool operator!=(const point_t& lhs, const point_t& rhs) { return !(lhs == rhs); - }; + } + + friend bool operator==(const point_t& lhs, const public_key& rhs) + { + return lhs.to_public_key() == rhs; + } + + friend bool operator!=(const point_t& lhs, const public_key& rhs) + { + return !(lhs == rhs); + } + + friend bool operator==(const public_key& lhs, const point_t& rhs) + { + return lhs == rhs.to_public_key(); + } + + friend bool operator!=(const public_key& lhs, const point_t& rhs) + { + return !(lhs == rhs); + } friend std::ostream& operator<<(std::ostream& ss, const point_t &v) { @@ -731,6 +835,11 @@ namespace crypto return pod_to_hex_comma_separated_uint64(pk); } + std::string to_comma_separated_int32_str() const + { + return pod_to_comma_separated_int32(m_p3); + } + }; // struct point_t @@ -739,10 +848,9 @@ namespace crypto // struct point_g_t : public point_t { - point_g_t() + explicit constexpr point_g_t(const int32_t(&v)[40]) noexcept + : point_t(v) { - scalar_t one(1); - ge_scalarmult_base(&m_p3, &one.m_s[0]); } friend point_t operator*(const scalar_t& lhs, const point_g_t&) @@ -886,6 +994,19 @@ namespace crypto scalar_t calc_hs() const; + void make_random() + { + for(size_t size = this->size(), i = 0; i < size; ++i) + at(i).make_random(); + } + + void resize_and_make_random(size_t size) + { + this->resize(size); + make_random(); + } + + }; // scalar_vec_t @@ -910,18 +1031,18 @@ namespace crypto // - // Global constants + // Global constants (checked in crypto_constants test) // - extern const point_g_t c_point_G; - - extern const point_t c_point_H; - extern const point_t c_point_H2; - extern const point_t c_point_X; - extern const point_t c_point_0; - extern const point_t c_point_H_plus_G; - extern const point_t c_point_H_minus_G; - + static constexpr point_t c_point_0 {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }}; + static constexpr point_g_t c_point_G {{ 25485296, 5318399, 8791791, -8299916, -14349720, 6939349, -3324311, -7717049, 7287234, -6577708, -758052, -1832720, 13046421, -4857925, 6576754, 14371947, -13139572, 6845540, -2198883, -4003719, -947565, 6097708, -469190, 10704810, -8556274, -15589498, -16424464, -16608899, 14028613, -5004649, 6966464, -2456167, 7033433, 6781840, 28785542, 12262365, -2659449, 13959020, -21013759, -5262166 }}; + static constexpr point_t c_point_H {{ 20574939, 16670001, -29137604, 14614582, 24883426, 3503293, 2667523, 420631, 2267646, -4769165, -11764015, -12206428, -14187565, -2328122, -16242653, -788308, -12595746, -8251557, -10110987, 853396, -4982135, 6035602, -21214320, 16156349, 977218, 2807645, 31002271, 5694305, -16054128, 5644146, -15047429, -568775, -22568195, -8089957, -27721961, -10101877, -29459620, -13359100, -31515170, -6994674 }}; + static constexpr point_t c_point_H2 {{ 1318371, 14804112, 12545972, -13482561, -12089798, -16020744, -21221907, -8410994, -33080606, 11275578, 3807637, 11185450, -23227561, -12892068, 1356866, -1025012, -8022738, -8139671, -20315029, -13916324, -6475650, -7025596, 12403179, -5139984, -12068178, 10445584, -14826705, -4927780, 13964546, 12525942, -2314107, -10566315, 32243863, 15603849, 5154154, 4276633, -20918372, -15718796, -26386151, 8434696 }}; + static constexpr point_t c_point_U {{ 30807552, 984924, 23426137, -5598760, 7545909, 16325843, 993742, 2594106, -31962071, -959867, 16454190, -4091093, 1197656, 13586872, -9269020, -14133290, 1869274, 13360979, -24627258, -10663086, 2212027, 1198856, 20515811, 15870563, -23833732, 9839517, -19416306, 11567295, -4212053, 348531, -2671541, 484270, -19128078, 1236698, -16002690, 9321345, 9776066, 10711838, 11187722, -16371275 }}; + static constexpr point_t c_point_X {{ 25635916, -5459446, 5768861, 5666160, -6357364, -12939311, 29490001, -4543704, -31266450, -2582476, 23705213, 9562626, -716512, 16560168, 7947407, 2039790, -2752711, 4742449, 3356761, 16338966, 17303421, -5790717, -5684800, 12062431, -3307947, 8139265, -26544839, 12058874, 3452748, 3359034, 26514848, -6060876, 31255039, 11154418, -21741975, -3782423, -19871841, 5729859, 21754676, -12454027 }}; + static constexpr point_t c_point_H_plus_G {{ 12291435, 3330843, -3390294, 13894858, -1099584, -6848191, 12040668, -15950068, -7494633, 12566672, -5526901, -16645799, -31081168, -1095427, -13082463, 4573480, -11255691, 4344628, 33477173, 11137213, -3837023, -12436594, -8471924, -814016, 10785607, 9492721, 10992667, 7406385, -5687296, -127915, -6229107, -9324867, 558657, 6493750, 4895261, 12642545, 9549220, 696086, 21894285, -10521807 }}; + static constexpr point_t c_point_H_minus_G {{ -28347682, 3523701, -3380175, -14453727, 4238027, -6032522, 20235758, 4091609, 12557126, -8064113, 4212476, -13419094, -114185, -7650727, -24238, 16663404, 23676363, -6819610, 18286466, 8714527, -3837023, -12436594, -8471924, -814016, 10785607, 9492721, 10992667, 7406385, -5687296, -127915, -20450317, 13815641, -11604061, -447489, 27380225, 9400847, -8551293, -1173627, -28110171, 14241295 }}; + // // hash functions' helper // @@ -1199,6 +1320,13 @@ namespace crypto return result; } + static point_t hp(const std::string& str) + { + point_t result; + ge_bytes_hash_to_ec(&result.m_p3, str.data(), str.size()); + return result; + } + }; // hash_helper_t struct diff --git a/src/crypto/one_out_of_many_proofs.cpp b/src/crypto/one_out_of_many_proofs.cpp new file mode 100644 index 00000000..6f6d8c9d --- /dev/null +++ b/src/crypto/one_out_of_many_proofs.cpp @@ -0,0 +1,322 @@ +// Copyright (c) 2023 Zano Project +// Copyright (c) 2023 sowle (val@zano.org, crypto.sowle@gmail.com) +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +#include "one_out_of_many_proofs.h" +#include "../currency_core/crypto_config.h" +#include "epee/include/misc_log_ex.h" + +//DISABLE_GCC_AND_CLANG_WARNING(unused-function) + +#if 0 +# define DBG_VAL_PRINT(x) std::cout << std::setw(30) << std::left << #x ": " << x << std::endl +# define DBG_PRINT(x) std::cout << x << std::endl +#else +# define DBG_VAL_PRINT(x) (void(0)) +# define DBG_PRINT(x) (void(0)) +#endif + +namespace crypto +{ + static const size_t N_max = 256; + static const size_t mn_max = 16; + + const point_t& get_BGE_generator(size_t index, bool& ok) + { + static std::vector precalculated_generators; + if (precalculated_generators.empty()) + { + precalculated_generators.resize(mn_max * 2); + + scalar_t hash_buf[2] = { hash_helper_t::hs("Zano BGE generator"), 0 }; + + for(size_t i = 0; i < precalculated_generators.size(); ++i) + { + hash_buf[1].m_u64[0] = i; + precalculated_generators[i] = hash_helper_t::hp(&hash_buf, sizeof hash_buf); + } + } + + if (index >= mn_max * 2) + { + ok = false; + return c_point_0; + } + + ok = true; + return precalculated_generators[index]; + } + + + + +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("generate_BGE_proof: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + bool generate_BGE_proof(const hash& context_hash, const std::vector& ring, const scalar_t& secret, const size_t secret_index, BGE_proof& result, uint8_t* p_err /* = nullptr */) + { + static constexpr size_t n = 4; // TODO: @#@# move it out + + DBG_PRINT(" - - - generate_BGE_proof - - -"); + size_t ring_size = ring.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(ring_size > 0, 0); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(secret_index < ring_size, 1); + +#ifndef NDEBUG + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(ring[secret_index] == secret * crypto::c_point_X, 2); +#endif + + + const size_t m = std::max(static_cast(1), constexpr_ceil_log_n(ring_size, n)); + const size_t N = constexpr_pow(m, n); + const size_t mn = m * n; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(N <= N_max, 3); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(mn <= mn_max, 4); + + scalar_mat_t a_mat(mn); // m x n matrix + a_mat.zero(); + std::vector l_digits(m); // l => n-ary gidits + size_t l = secret_index; + for(size_t j = 0; j < m; ++j) + { + for(size_t i = n - 1; i != 0; --i) // [n - 1; 1] + { + a_mat(j, i).make_random(); + a_mat(j, 0) -= a_mat(j, i); // a[j; 0] = -sum( a[j; i] ), i in [1; n-1] + } + + size_t digit = l % n; // j-th digit of secret_index + l_digits[j] = digit; + l = l / n; + } + +#ifndef NDEBUG + for(size_t j = 0; j < m; ++j) + { + scalar_t a_sum{}; + for(size_t i = 0; i != n; ++i) + a_sum += a_mat(j, i); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(a_sum.is_zero(), 230); + } +#endif + + // + // coeffs calculation (naive implementation, consider optimization in future) + // + scalar_vec_t coeffs(N * m); // m x N matrix + coeffs.zero(); + for(size_t i = 0; i < N; ++i) + { + coeffs[i] = c_scalar_1; // first row is (1, ..., 1) + size_t i_tmp = i; + size_t m_bound = 1; + for(size_t j = 0; j < m; ++j) + { + size_t i_j = i_tmp % n; // j-th digit of i + i_tmp /= n; + + if (i_j == l_digits[j]) // true if j-th digits of i and l matches + { + scalar_t carry{}; + for(size_t k = 0; k < m_bound; ++k) + { + scalar_t old = coeffs[k * N + i]; + coeffs[k * N + i] *= a_mat(j, i_j); + coeffs[k * N + i] += carry; + carry = old; + } + if (m_bound < m) + coeffs[m_bound * N + i] += carry; + ++m_bound; + } + else + { + for(size_t k = 0; k < m_bound; ++k) + coeffs[k * N + i] *= a_mat(j, i_j); + } + } + } + + scalar_t r_A = scalar_t::random(); + scalar_t r_B = scalar_t::random(); + scalar_vec_t ro(m); + ro.make_random(); + + point_t A = c_point_0; + point_t B = c_point_0; + + result.Pk.clear(); + + bool r = false, r2 = false; + for(size_t j = 0; j < m; ++j) + { + for(size_t i = 0; i < n; ++i) + { + const point_t& gen_1 = get_BGE_generator((j * n + i) * 2 + 0, r); + const point_t& gen_2 = get_BGE_generator((j * n + i) * 2 + 1, r2); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(r && r2, 5); + const scalar_t& a = a_mat(j, i); + A += a * gen_1 - a * a * gen_2; + if (l_digits[j] == i) + B += gen_1 - a * gen_2; + else + B += a * gen_2; + } + + point_t Pk = c_point_0; + for(size_t i = 0; i < ring_size; ++i) + Pk += coeffs[j * N + i] * ring[i]; + for(size_t i = ring_size; i < N; ++i) + Pk += coeffs[j * N + i] * ring[ring_size - 1]; + + Pk += ro[j] * c_point_X; + result.Pk.emplace_back(std::move((c_scalar_1div8 * Pk).to_public_key())); + } + + A += r_A * c_point_X; + result.A = (c_scalar_1div8 * A).to_public_key(); + B += r_B * c_point_X; + result.B = (c_scalar_1div8 * B).to_public_key(); + + hash_helper_t::hs_t hsc(1 + ring_size + 2 + m); + hsc.add_hash(context_hash); + for(auto& ring_el : ring) + hsc.add_point(c_scalar_1div8 * ring_el); + hsc.add_pub_key(result.A); + hsc.add_pub_key(result.B); + hsc.add_pub_keys_array(result.Pk); + scalar_t x = hsc.calc_hash(); + DBG_VAL_PRINT(x); + + result.f.resize(m * (n - 1)); + for(size_t j = 0; j < m; ++j) + { + for(size_t i = 1; i < n; ++i) + { + result.f[j * (n - 1) + i - 1] = a_mat(j, i); + if (l_digits[j] == i) + result.f[j * (n - 1) + i - 1] += x; + } + } + + result.y = r_A + x * r_B; + + result.z = 0; + scalar_t x_power = c_scalar_1; + for(size_t k = 0; k < m; ++k) + { + result.z -= x_power * ro[k]; + x_power *= x; + } + result.z += secret * x_power; + + return true; + } +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + + //--------------------------------------------------------------- + + +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("generate_BGE_proof: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + bool verify_BGE_proof(const hash& context_hash, const std::vector& ring, const BGE_proof& sig, uint8_t* p_err /* = nullptr */) + { + static constexpr size_t n = 4; // TODO: @#@# move it out + + DBG_PRINT(" - - - verify_BGE_proof - - -"); + size_t ring_size = ring.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(ring_size > 0, 0); + + const size_t m = std::max(static_cast(1), constexpr_ceil_log_n(ring_size, n)); + const size_t N = constexpr_pow(m, n); + const size_t mn = m * n; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.Pk.size() == m, 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.f.size() == m * (n - 1), 2); + + hash_helper_t::hs_t hsc(1 + ring_size + 2 + m); + hsc.add_hash(context_hash); + for(const public_key* ppk : ring) + hsc.add_pub_key(*ppk); + hsc.add_pub_key(sig.A); + hsc.add_pub_key(sig.B); + hsc.add_pub_keys_array(sig.Pk); + scalar_t x = hsc.calc_hash(); + DBG_VAL_PRINT(x); + + scalar_vec_t f0(m); // the first column f_{i,0} = x - sum{j=1}{n-1}( f_{i,j} ) + for(size_t j = 0; j < m; ++j) + { + f0[j] = x; + for(size_t i = 1; i < n; ++i) + f0[j] -= sig.f[j * (n - 1) + i - 1]; + } + + // + // 1 + // + point_t A = point_t(sig.A).modify_mul8(); + point_t B = point_t(sig.B).modify_mul8(); + + point_t Z = A + x * B; + + bool r = false, r2 = false; + for(size_t j = 0; j < m; ++j) + { + for(size_t i = 0; i < n; ++i) + { + const point_t& gen_1 = get_BGE_generator((j * n + i) * 2 + 0, r); + const point_t& gen_2 = get_BGE_generator((j * n + i) * 2 + 1, r2); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(r && r2, 5); + const scalar_t& f_ji = (i == 0) ? f0[j] : sig.f[j * (n - 1) + i - 1]; + + Z -= f_ji * gen_1 + f_ji * (x - f_ji) * gen_2; + } + } + Z -= sig.y * c_point_X; + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(Z.is_zero(), 100); + + // + // 2 + // + scalar_vec_t p_vec(N); + for(size_t i = 0; i < N; ++i) + { + p_vec[i] = c_scalar_1; + size_t i_tmp = i; + for(size_t j = 0; j < m; ++j) + { + size_t i_j = i_tmp % n; // j-th digit of i + i_tmp /= n; + const scalar_t& f_jij = (i_j == 0) ? f0[j] : sig.f[j * (n - 1) + i_j - 1]; + p_vec[i] *= f_jij; + } + } + + for(size_t i = 0; i < ring_size; ++i) + Z += p_vec[i] * point_t(*ring[i]).modify_mul8(); + for(size_t i = ring_size; i < N; ++i) + Z += p_vec[i] * point_t(*ring[ring_size - 1]).modify_mul8(); + + scalar_t x_power = c_scalar_1; + for(size_t k = 0; k < m; ++k) + { + Z -= x_power * point_t(sig.Pk[k]).modify_mul8(); + x_power *= x; + } + + Z -= sig.z * c_point_X; + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(Z.is_zero(), 101); + + return true; + } + +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + + +} // namespace crypto diff --git a/src/crypto/one_out_of_many_proofs.h b/src/crypto/one_out_of_many_proofs.h new file mode 100644 index 00000000..b52e8de8 --- /dev/null +++ b/src/crypto/one_out_of_many_proofs.h @@ -0,0 +1,40 @@ +// Copyright (c) 2023 Zano Project +// Copyright (c) 2023 sowle (val@zano.org, crypto.sowle@gmail.com) +// Distributed under the MIT/X11 software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. +// +#pragma once +#include "crypto-sugar.h" + +namespace crypto +{ + // + // BGE stands for Bootle, Groth, Esgin + // + // This is a proof-of-concept implementation of a log-size one-out-of-many proof based on ideas and approaches by Bootle et al, Groth et al and Esgin et al + // + // https://eprint.iacr.org/2014/764 + // https://eprint.iacr.org/2015/643 + // https://eprint.iacr.org/2019/1287 + // + // Disclaimer: shouldn't be used in production code until the security proofs and the code are peer-reviewed. + // + + // m+2 group elements, m(n-1)+2 field elements + struct BGE_proof + { + public_key A; // premultiplied by 1/8 + public_key B; // premultiplied by 1/8 + std::vector Pk; // premultiplied by 1/8, size = m + scalar_vec_t f; // size = m * (n - 1) + scalar_t y; + scalar_t z; + }; + + bool generate_BGE_proof(const hash& context_hash, const std::vector& ring, const scalar_t& secret, const size_t secret_index, BGE_proof& result, uint8_t* p_err = nullptr); + + + bool verify_BGE_proof(const hash& context_hash, const std::vector& ring, const BGE_proof& sig, uint8_t* p_err = nullptr); + + +} // namespace crypto diff --git a/src/crypto/range_proof_bpp.h b/src/crypto/range_proof_bpp.h index fb85def3..77dcb1a7 100644 --- a/src/crypto/range_proof_bpp.h +++ b/src/crypto/range_proof_bpp.h @@ -23,15 +23,21 @@ namespace crypto scalar_t delta; }; -#define DBG_VAL_PRINT(x) (void(0)) //*/ std::cout << #x ": " << x << ENDL -#define DBG_PRINT(x) (void(0)) //*/ std::cout << x << ENDL +#if 0 +# define DBG_VAL_PRINT(x) std::cout << std::setw(30) << std::left << #x ": " << x << std::endl +# define DBG_PRINT(x) std::cout << x << std::endl +#else +# define DBG_VAL_PRINT(x) (void(0)) +# define DBG_PRINT(x) (void(0)) +#endif + #define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ if (!(cond)) { LOG_PRINT_RED("bpp_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ if (p_err) { *p_err = err_code; } return false; } - template> + template bool bpp_gen(const scalar_vec_t& values, const scalar_vec_t& masks, const std::vector& commitments_1div8, bpp_signature& sig, uint8_t* p_err = nullptr) { // Note: commitments_1div8 are supposed to be already calculated @@ -44,6 +50,15 @@ namespace crypto const size_t c_bpp_mn = c_bpp_m * CT::c_bpp_n; const size_t c_bpp_log2_mn = c_bpp_log2_m + CT::c_bpp_log2_n; +#ifndef NDEBUG + for(size_t i = 0; i < values.size(); ++i) + { + point_t V{}; + CT::calc_pedersen_commitment(values[i], masks[i], V); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(point_t(*commitments_1div8[i]).modify_mul8() == V, 4); + } +#endif + // s.a. BP+ paper, page 15, eq. 11 // decompose v into aL and aR: // v = aL o (1, 2, 2^2, ..., 2^n-1), o - component-wise product aka Hadamard product @@ -322,7 +337,7 @@ namespace crypto // convenient overload for tests - template> + template bool bpp_gen(const scalar_vec_t& values, const scalar_vec_t& masks, bpp_signature& sig, std::vector& commitments_1div8_to_be_generated, uint8_t* p_err = nullptr) { CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(values.size() == masks.size(), 91); @@ -351,7 +366,7 @@ namespace crypto }; - template> + template bool bpp_verify(const std::vector& sigs, uint8_t* p_err = nullptr) { #define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ diff --git a/src/crypto/range_proof_bppe.h b/src/crypto/range_proof_bppe.h index 3b2158d0..260132b0 100644 --- a/src/crypto/range_proof_bppe.h +++ b/src/crypto/range_proof_bppe.h @@ -24,12 +24,17 @@ namespace crypto scalar_t delta_2; }; -#define DBG_VAL_PRINT(x) (void(0)) // std::cout << #x ": " << x << ENDL -#define DBG_PRINT(x) (void(0)) // std::cout << x << ENDL +#if 0 +# define DBG_VAL_PRINT(x) std::cout << std::setw(30) << std::left << #x ": " << x << std::endl +# define DBG_PRINT(x) std::cout << x << std::endl +#else +# define DBG_VAL_PRINT(x) (void(0)) +# define DBG_PRINT(x) (void(0)) +#endif #define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ - if (!(cond)) { LOG_PRINT_RED("bppe_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ - if (p_err) { *p_err = err_code; } return false; } + if (!(cond)) { LOG_PRINT_RED("bppe_gen: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } template @@ -45,6 +50,15 @@ namespace crypto const size_t c_bpp_mn = c_bpp_m * CT::c_bpp_n; const size_t c_bpp_log2_mn = c_bpp_log2_m + CT::c_bpp_log2_n; +#ifndef NDEBUG + for(size_t i = 0; i < values.size(); ++i) + { + point_t V{}; + CT::calc_pedersen_commitment_2(values[i], masks[i], masks2[i], V); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(point_t(*commitments_1div8[i]).modify_mul8() == V, 4); + } +#endif + // s.a. BP+ paper, page 15, eq. 11 // decompose v into aL and aR: // v = aL o (1, 2, 2^2, ..., 2^n-1), o - component-wise product aka Hadamard product diff --git a/src/crypto/range_proofs.cpp b/src/crypto/range_proofs.cpp index 783a0c2b..fd14ec0f 100644 --- a/src/crypto/range_proofs.cpp +++ b/src/crypto/range_proofs.cpp @@ -6,4 +6,12 @@ namespace crypto { + // TODO @#@# redesign needed, consider changing to inline constexpr + const point_t& bpp_ct_generators_HGX::bpp_G = c_point_H; + const point_t& bpp_ct_generators_HGX::bpp_H = c_point_G; + const point_t& bpp_ct_generators_HGX::bpp_H2 = c_point_X; + + const point_t& bpp_ct_generators_UGX::bpp_G = c_point_U; + const point_t& bpp_ct_generators_UGX::bpp_H = c_point_G; + const point_t& bpp_ct_generators_UGX::bpp_H2 = c_point_X; } diff --git a/src/crypto/range_proofs.h b/src/crypto/range_proofs.h index 4e25ed79..95cbbc43 100644 --- a/src/crypto/range_proofs.h +++ b/src/crypto/range_proofs.h @@ -1,5 +1,5 @@ -// Copyright (c) 2021-2022 Zano Project (https://zano.org/) -// Copyright (c) 2021-2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2021-2023 Zano Project (https://zano.org/) +// Copyright (c) 2021-2023 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #pragma once @@ -23,23 +23,6 @@ namespace crypto return result; } - - // returns greatest k, s.t. 2**k <= v - // tests in crypto_tests_range_proofs.h - constexpr size_t constexpr_floor_log2(size_t v) - { - return v <= 1 ? 0 : constexpr_floor_log2(v >> 1) + 1; - } - - // returns smallest k, s.t. v <= 2**k - // tests in crypto_tests_range_proofs.h - constexpr size_t constexpr_ceil_log2(size_t v) - { - return v <= 1 ? 0 : constexpr_floor_log2(v - 1) + 1; - } - - - // returns least significant bit uing de Bruijn sequence // http://graphics.stanford.edu/~seander/bithacks.html inline uint8_t calc_lsb_32(uint32_t v) @@ -56,8 +39,25 @@ namespace crypto //////////////////////////////////////// // crypto trait for Zano //////////////////////////////////////// - template - struct bpp_crypto_trait_zano + struct bpp_ct_generators_HGX + { + // NOTE! This notation follows the original BP+ whitepaper, see mapping to Zano's generators below + static const point_t& bpp_G; + static const point_t& bpp_H; + static const point_t& bpp_H2; + }; + + struct bpp_ct_generators_UGX + { + // NOTE! This notation follows the original BP+ whitepaper, see mapping to Zano's generators below + static const point_t& bpp_G; + static const point_t& bpp_H; + static const point_t& bpp_H2; + }; + + + template + struct bpp_crypto_trait_zano : gen_trait_t { static constexpr size_t c_bpp_n = N; // the upper bound for the witness's range static constexpr size_t c_bpp_values_max = values_max; // maximum number of elements in BP+ proof, i.e. max allowed BP+ outputs @@ -66,12 +66,14 @@ namespace crypto static void calc_pedersen_commitment(const scalar_t& value, const scalar_t& mask, point_t& commitment) { - commitment = value * bpp_G + mask * bpp_H; + // commitment = value * bpp_G + mask * bpp_H + commitment = operator*(value, bpp_G) + mask * bpp_H; } static void calc_pedersen_commitment_2(const scalar_t& value, const scalar_t& mask1, const scalar_t& mask2, point_t& commitment) { - commitment = value * bpp_G + mask1 * bpp_H + mask2 * bpp_H2; + // commitment = value * bpp_G + mask1 * bpp_H * mask2 * bpp_H2 + commitment = operator*(value, bpp_G) + mask1 * bpp_H + mask2 * bpp_H2; } static const scalar_t& get_initial_transcript() @@ -98,6 +100,7 @@ namespace crypto } // TODO: refactor with proper OOB handling + // TODO: @#@# add domain separation static const point_t& get_generator(bool select_H, size_t index) { if (index >= c_bpp_mn_max) @@ -125,19 +128,15 @@ namespace crypto return result; } - static const point_t& bpp_G; // NOTE! This notation follows original BP+ whitepaper, see mapping to Zano's generators below - static const point_t& bpp_H; - static const point_t& bpp_H2; + using gen_trait_t::bpp_G; + using gen_trait_t::bpp_H; + using gen_trait_t::bpp_H2; }; // struct bpp_crypto_trait_zano - template - const point_t& bpp_crypto_trait_zano::bpp_G = c_point_H; - template - const point_t& bpp_crypto_trait_zano::bpp_H = c_point_G; + typedef bpp_crypto_trait_zano bpp_crypto_trait_ZC_out; - template - const point_t& bpp_crypto_trait_zano::bpp_H2 = c_point_X; + typedef bpp_crypto_trait_zano bpp_crypto_trait_Zarcanum; // efficient multiexponentiation (naive stub implementation atm, TODO) diff --git a/src/crypto/zarcanum.cpp b/src/crypto/zarcanum.cpp index 285ac505..043f4113 100644 --- a/src/crypto/zarcanum.cpp +++ b/src/crypto/zarcanum.cpp @@ -1,22 +1,35 @@ -// Copyright (c) 2022 Zano Project -// Copyright (c) 2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2022-2023 Zano Project +// Copyright (c) 2022-2023 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // // Note: This file originates from tests/functional_tests/crypto_tests.cpp #include "epee/include/misc_log_ex.h" #include "zarcanum.h" -#include "crypto/range_proofs.h" -#include "../currency_core/crypto_config.h" // TODO: move it to the crypto +#include "range_proofs.h" +#include "../currency_core/crypto_config.h" // TODO: move it to the crypto +#include "../common/crypto_stream_operators.h" // TODO: move it to the crypto + +#if 0 +# define DBG_VAL_PRINT(x) std::cout << std::setw(30) << std::left << #x ": " << x << std::endl +# define DBG_PRINT(x) std::cout << x << std::endl +#else +# define DBG_VAL_PRINT(x) (void(0)) +# define DBG_PRINT(x) (void(0)) +#endif namespace crypto { const scalar_t c_zarcanum_z_coeff_s = { 0, 1, 0, 0 }; // c_scalar_2p64 const mp::uint256_t c_zarcanum_z_coeff_mp = c_zarcanum_z_coeff_s.as_boost_mp_type(); - #define DBG_VAL_PRINT(x) (void(0)) // std::cout << #x ": " << x << std::endl - #define DBG_PRINT(x) (void(0)) // std::cout << x << std::endl - + template + inline std::ostream &operator <<(std::ostream &o, const std::vector &v) + { + for(size_t i = 0, n = v.size(); i < n; ++i) + o << ENDL << " [" << std::setw(2) << i << "]: " << v[i]; + return o; + } mp::uint256_t zarcanum_precalculate_l_div_z_D(const mp::uint128_t& pos_difficulty) { @@ -50,9 +63,9 @@ namespace crypto if (!(cond)) { LOG_PRINT_RED("zarcanum_generate_proof: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ if (p_err) { *p_err = err_code; } return false; } - bool zarcanum_generate_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, + bool zarcanum_generate_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, const scalar_t& last_pow_block_id_hashed, const key_image& stake_ki, - const scalar_t& secret_x, const scalar_t& secret_q, uint64_t secret_index, const scalar_t& pseudo_out_blinding_mask, uint64_t stake_amount, const scalar_t& stake_blinding_mask, + const scalar_t& secret_x, const scalar_t& secret_q, uint64_t secret_index, const crypto::scalar_t& stake_out_asset_id_blinding_mask, const scalar_t& pseudo_out_blinding_mask, uint64_t stake_amount, const scalar_t& stake_blinding_mask, zarcanum_proof& result, uint8_t* p_err /* = nullptr */) { DBG_PRINT("zarcanum_generate_proof"); @@ -123,10 +136,10 @@ namespace crypto const scalar_vec_t masks = { bf }; // G component const scalar_vec_t masks2 = { bx }; // X component const std::vector E_1div8_vec_ptr = { &result.E }; - - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bppe_gen>(values, masks, masks2, E_1div8_vec_ptr, result.E_range_proof), 10); - // = four-layers ring signature data outline = + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bppe_gen(values, masks, masks2, E_1div8_vec_ptr, result.E_range_proof), 10); + + // = five-layers ring signature data outline = // (j in [0, ring_size-1]) // layer 0 ring // A[j] ( = ring[j].stealth_address) @@ -140,25 +153,35 @@ namespace crypto // layer 1 secret (with respect to G) // stake_blinding_mask - pseudo_out_blinding_mask // - // additional layers for Zarcanum: + // additional layer for confidential assets: // // layer 2 ring - // C - A[j] - Q[j] + // ring[j].blinded_asset_id - pseudo_out_blinded_asset_id // layer 2 secret (with respect to X) - // x0 + // -pseudo_out_asset_id_blinding_mask ( = -r'_i ) + // + // additional layers for Zarcanum: // // layer 3 ring + // C - A[j] - Q[j] + // layer 3 secret (with respect to X) + // x0 + // + // layer 4 ring // Q[j] - // layer 3 secret (with respect to G) + // layer 4 secret (with respect to G) // secret_q - point_t pseudo_out_amount_commitment = a * crypto::c_point_H + pseudo_out_blinding_mask * crypto::c_point_G; + // such pseudo_out_asset_id_blinding_mask effectively makes pseudo_out_blinded_asset_id == currency::native_coin_asset_id_pt == crypto::point_H + scalar_t pseudo_out_asset_id_blinding_mask = -stake_out_asset_id_blinding_mask; // T^p_i = T_i + (-r_i) * X = H_i + + point_t pseudo_out_amount_commitment = a * crypto::c_point_H + pseudo_out_blinding_mask * crypto::c_point_G; // A^p_i = a_i * H_i + f'_i * G result.pseudo_out_amount_commitment = (crypto::c_scalar_1div8 * pseudo_out_amount_commitment).to_public_key(); TRY_ENTRY() - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(generate_CLSAG_GGXG(m, ring, pseudo_out_amount_commitment, C, stake_ki, - secret_x, stake_blinding_mask - pseudo_out_blinding_mask, x0, secret_q, secret_index, - result.clsag_ggxg), 20); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(generate_CLSAG_GGXXG(m, ring, pseudo_out_amount_commitment, crypto::c_point_H, C, stake_ki, + secret_x, stake_blinding_mask - pseudo_out_blinding_mask, -pseudo_out_asset_id_blinding_mask, x0, secret_q, secret_index, + result.clsag_ggxxg), 20); CATCH_ENTRY2(false); return true; @@ -166,61 +189,68 @@ namespace crypto #undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE - + #define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ if (!(cond)) { LOG_PRINT_RED("zarcanum_verify_proof: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ if (p_err) { *p_err = err_code; } return false; } - bool zarcanum_verify_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, + bool zarcanum_verify_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, const scalar_t& last_pow_block_id_hashed, const key_image& stake_ki, const mp::uint128_t& pos_difficulty, - const zarcanum_proof& sig, uint8_t* p_err /* = nullptr */) + const zarcanum_proof& sig, uint8_t* p_err /* = nullptr */) noexcept { - DBG_PRINT("zarcanum_verify_proof"); - bool r = false; + TRY_ENTRY() + { + DBG_PRINT("zarcanum_verify_proof"); + bool r = false; - // make sure 0 < d <= l / floor(z * D) - const mp::uint256_t l_div_z_D_mp = crypto::zarcanum_precalculate_l_div_z_D(pos_difficulty); - const scalar_t l_div_z_D(l_div_z_D_mp); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(!sig.d.is_zero() && sig.d < l_div_z_D, 2); - const scalar_t dz = sig.d * c_zarcanum_z_coeff_s; + // make sure 0 < d <= l / floor(z * D) + const mp::uint256_t l_div_z_D_mp = crypto::zarcanum_precalculate_l_div_z_D(pos_difficulty); + const scalar_t l_div_z_D(l_div_z_D_mp); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(!sig.d.is_zero() && sig.d < l_div_z_D, 2); + const scalar_t dz = sig.d * c_zarcanum_z_coeff_s; - // calculate h - const scalar_t h = scalar_t(kernel_hash); + // calculate h + const scalar_t h = scalar_t(kernel_hash); - // calculate F - point_t C_prime = point_t(sig.C_prime); - C_prime.modify_mul8(); - point_t C = point_t(sig.C); - C.modify_mul8(); - point_t E = point_t(sig.E); - E.modify_mul8(); - point_t F = h * C_prime - dz * C + E + last_pow_block_id_hashed * h * c_point_H; + // calculate F + point_t C_prime = point_t(sig.C_prime); + C_prime.modify_mul8(); + point_t C = point_t(sig.C); + C.modify_mul8(); + point_t E = point_t(sig.E); + E.modify_mul8(); + point_t F = h * C_prime - dz * C + E + last_pow_block_id_hashed * h * c_point_H; - DBG_VAL_PRINT(h); DBG_VAL_PRINT(last_pow_block_id_hashed); DBG_VAL_PRINT(dz); - DBG_VAL_PRINT(C); DBG_VAL_PRINT(C_prime); DBG_VAL_PRINT(E); DBG_VAL_PRINT(F); + DBG_VAL_PRINT(h); DBG_VAL_PRINT(last_pow_block_id_hashed); DBG_VAL_PRINT(dz); + DBG_VAL_PRINT(C); DBG_VAL_PRINT(C_prime); DBG_VAL_PRINT(E); DBG_VAL_PRINT(F); - // check three proofs with a shared Fiat-Shamir challenge c - point_t C_plus_C_prime = C + C_prime; - point_t C_minus_C_prime = C - C_prime; - hash_helper_t::hs_t hash_calc(7); - hash_calc.add_32_chars(CRYPTO_HDS_ZARCANUM_PROOF_HASH); - hash_calc.add_point(sig.y0 * c_point_X + sig.y1 * c_point_H_plus_G - sig.c * C_plus_C_prime); // y_0 * X + y1 (H + G) - c (C + C') - hash_calc.add_point(sig.y2 * c_point_X + sig.y3 * c_point_H_minus_G - sig.c * C_minus_C_prime); // y_2 * X + y3 (H - G) - c (C - C') - hash_calc.add_point(sig.y4 * c_point_X - sig.c * F); // y_4 * X - c * F - hash_calc.add_point(C_plus_C_prime); - hash_calc.add_point(C_minus_C_prime); - hash_calc.add_point(F); - scalar_t c_prime = hash_calc.calc_hash(); - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.c == c_prime, 3); + // check three proofs with a shared Fiat-Shamir challenge c + point_t C_plus_C_prime = C + C_prime; + point_t C_minus_C_prime = C - C_prime; + hash_helper_t::hs_t hash_calc(7); + hash_calc.add_32_chars(CRYPTO_HDS_ZARCANUM_PROOF_HASH); + hash_calc.add_point(sig.y0 * c_point_X + sig.y1 * c_point_H_plus_G - sig.c * C_plus_C_prime); // y_0 * X + y1 (H + G) - c (C + C') + hash_calc.add_point(sig.y2 * c_point_X + sig.y3 * c_point_H_minus_G - sig.c * C_minus_C_prime); // y_2 * X + y3 (H - G) - c (C - C') + hash_calc.add_point(sig.y4 * c_point_X - sig.c * F); // y_4 * X - c * F + hash_calc.add_point(C_plus_C_prime); + hash_calc.add_point(C_minus_C_prime); + hash_calc.add_point(F); + scalar_t c_prime = hash_calc.calc_hash(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.c == c_prime, 3); - // check extended range proof for E - std::vector E_for_range_proof = { point_t(sig.E) }; // consider changing to 8*sig.E to avoid additional conversion - std::vector range_proofs = { bppe_sig_commit_ref_t(sig.E_range_proof, E_for_range_proof) }; - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bppe_verify>(range_proofs), 10); + // check extended range proof for E + std::vector E_for_range_proof = { point_t(sig.E) }; // consider changing to 8*sig.E to avoid additional conversion + std::vector range_proofs = { bppe_sig_commit_ref_t(sig.E_range_proof, E_for_range_proof) }; - // check extended CLSAG-GGXG ring signature - CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(verify_CLSAG_GGXG(m, ring, sig.pseudo_out_amount_commitment, sig.C, stake_ki, sig.clsag_ggxg), 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(bppe_verify(range_proofs), 10); + + static public_key native_coin_asset_id = (crypto::c_scalar_1div8 * crypto::c_point_H).to_public_key(); // consider making it less ugly -- sowle + + // check extended CLSAG-GGXG ring signature + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(verify_CLSAG_GGXXG(m, ring, sig.pseudo_out_amount_commitment, native_coin_asset_id, sig.C, stake_ki, sig.clsag_ggxxg), 1); + } + CATCH_ENTRY_CUSTOM2({if (p_err) *p_err = 100;}, false) return true; } @@ -228,5 +258,149 @@ namespace crypto #undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("generate_vector_UG_aggregation_proof: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + bool generate_vector_UG_aggregation_proof(const hash& m, const scalar_vec_t& u_secrets, const scalar_vec_t& g_secrets0, const scalar_vec_t& g_secrets1, + const std::vector& amount_commitments, + const std::vector& amount_commitments_for_rp_aggregation, + const std::vector& blinded_asset_ids, + vector_UG_aggregation_proof& result, uint8_t* p_err /* = nullptr */) + { + // w - public random weighting factor + // proof of knowing e_j and y'' in zero knowledge in the following eq: + // E_j + w * E'_j = e_j * (T'_j + w * U) + (y_j + w * y'_j) * G + // where: + // e_j -- output's amount + // T'_j -- output's blinded asset tag + // E_j == e_j * T'_j + y_j * G -- output's amount commitments + // E'_j == e_j * U + y'_j * G -- additional commitment to the same amount for range proof aggregation + + // amount_commitments[j] + w * amount_commitments_for_rp_aggregation[j] + // == + // u_secrets[j] * (blinded_asset_ids[j] + w * U) + (g_secrets0[j] + w * g_secrets1[j]) * G + + const size_t n = u_secrets.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n != 0, 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n == g_secrets0.size(), 2); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n == g_secrets1.size(), 3); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n == amount_commitments.size(), 4); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n == amount_commitments_for_rp_aggregation.size(), 5); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n == blinded_asset_ids.size(), 6); + + crypto::hash_helper_t::hs_t hash_calculator(1 + 3 * n); + hash_calculator.add_hash(m); + hash_calculator.add_points_array(amount_commitments); + hash_calculator.add_points_array(amount_commitments_for_rp_aggregation); + scalar_t w = hash_calculator.calc_hash(false); // don't clean the buffer + DBG_VAL_PRINT(w); + +#ifndef NDEBUG + for(size_t j = 0; j < n; ++j) + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(amount_commitments[j] + w * amount_commitments_for_rp_aggregation[j] == u_secrets[j] * (blinded_asset_ids[j] + w * crypto::c_point_U) + (g_secrets0[j] + w * g_secrets1[j]) * c_point_G, 20); +#endif + + result.amount_commitments_for_rp_aggregation.clear(); + result.y0s.clear(); + result.y1s.clear(); + + crypto::scalar_vec_t r0, r1; + r0.resize_and_make_random(n); + r1.resize_and_make_random(n); + + std::vector asset_tag_plus_U_vec(n); + for(size_t j = 0; j < n; ++j) + asset_tag_plus_U_vec[j] = blinded_asset_ids[j] + w * crypto::c_point_U; + + std::vector R(n); + for(size_t j = 0; j < n; ++j) + R[j].assign_mul_plus_G(r0[j], asset_tag_plus_U_vec[j], r1[j]); // R[j] = r0[j] * asset_tag_plus_U_vec[j] + r1[j] * G + + hash_calculator.add_points_array(R); + result.c = hash_calculator.calc_hash(); + + DBG_VAL_PRINT(asset_tag_plus_U_vec); DBG_VAL_PRINT(m); DBG_VAL_PRINT(amount_commitments); DBG_VAL_PRINT(amount_commitments_for_rp_aggregation); DBG_VAL_PRINT(R); + DBG_VAL_PRINT(result.c); + + for(size_t j = 0; j < n; ++j) + { + result.y0s.emplace_back(r0[j] - result.c * u_secrets[j]); + result.y1s.emplace_back(r1[j] - result.c * (g_secrets0[j] + w * g_secrets1[j])); + result.amount_commitments_for_rp_aggregation.emplace_back((crypto::c_scalar_1div8 * amount_commitments_for_rp_aggregation[j]).to_public_key()); + } + + return true; + } +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + + +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("verify_vector_UG_aggregation_proof: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << (int)err_code, LOG_LEVEL_3); \ + if (p_err) { *p_err = err_code; } return false; } + + bool verify_vector_UG_aggregation_proof(const hash& m, const std::vector amount_commitments_1div8, const std::vector blinded_asset_ids_1div8, + const vector_UG_aggregation_proof& sig, uint8_t* p_err /* = nullptr */) noexcept + { + TRY_ENTRY() + { + const size_t n = amount_commitments_1div8.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n > 0, 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(blinded_asset_ids_1div8.size() == n, 2); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.amount_commitments_for_rp_aggregation.size() == n, 3); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.y0s.size() == n, 4); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.y1s.size() == n, 5); + + crypto::hash_helper_t::hs_t hash_calculator(1 + 3 * n); + hash_calculator.add_hash(m); + DBG_VAL_PRINT(m); + + std::vector amount_commitments_pt; + for(size_t j = 0; j < n; ++j) + { + point_t A = crypto::point_t(*amount_commitments_1div8[j]).modify_mul8(); + hash_calculator.add_point(A); + amount_commitments_pt.emplace_back(A); + DBG_VAL_PRINT(A); + } + + std::vector amount_commitments_for_rp_aggregation_pt; + for(size_t j = 0; j < n; ++j) + { + point_t Arpa = crypto::point_t(sig.amount_commitments_for_rp_aggregation[j]).modify_mul8(); + hash_calculator.add_point(Arpa); // TODO @#@ performance: consider adding premultiplied by 1/8 points to the hash + amount_commitments_for_rp_aggregation_pt.emplace_back(Arpa); + DBG_VAL_PRINT(Arpa); + } + + scalar_t w = hash_calculator.calc_hash(false); // don't clear the buffer + DBG_VAL_PRINT(w); + + std::vector asset_tag_plus_U_vec(n); + for(size_t j = 0; j < n; ++j) + asset_tag_plus_U_vec[j] = crypto::point_t(*blinded_asset_ids_1div8[j]).modify_mul8() + w * crypto::c_point_U; + DBG_VAL_PRINT(asset_tag_plus_U_vec); + + for(size_t j = 0; j < n; ++j) + { + hash_calculator.add_pub_key(crypto::point_t( + sig.y0s[j] * asset_tag_plus_U_vec[j] + + sig.y1s[j] * crypto::c_point_G + + sig.c * (amount_commitments_pt[j] + w * amount_commitments_for_rp_aggregation_pt[j]) + ).to_public_key()); + DBG_VAL_PRINT(hash_calculator.m_elements.back().pk); + } + + crypto::scalar_t c = hash_calculator.calc_hash(); + DBG_VAL_PRINT(c); DBG_VAL_PRINT(sig.c); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(sig.c == c, 0); + } + CATCH_ENTRY_CUSTOM2({if (p_err) *p_err = 100; }, false) + + return true; + } +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE + + } // namespace crypto diff --git a/src/crypto/zarcanum.h b/src/crypto/zarcanum.h index 0562efd0..9f881c28 100644 --- a/src/crypto/zarcanum.h +++ b/src/crypto/zarcanum.h @@ -1,13 +1,13 @@ -// Copyright (c) 2022 Zano Project -// Copyright (c) 2022 sowle (val@zano.org, crypto.sowle@gmail.com) +// Copyright (c) 2022-2023 Zano Project +// Copyright (c) 2022-2023 sowle (val@zano.org, crypto.sowle@gmail.com) // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // // Note: This file originates from tests/functional_tests/crypto_tests.cpp #pragma once #include "crypto-sugar.h" -#include "crypto/range_proofs.h" -#include "crypto/clsag.h" +#include "range_proofs.h" +#include "clsag.h" #include namespace crypto @@ -41,18 +41,149 @@ namespace crypto bppe_signature E_range_proof; crypto::public_key pseudo_out_amount_commitment; // premultiplied by 1/8 - CLSAG_GGXG_signature clsag_ggxg; + CLSAG_GGXXG_signature clsag_ggxxg; }; - bool zarcanum_generate_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, + bool zarcanum_generate_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, const scalar_t& last_pow_block_id_hashed, const key_image& stake_ki, - const scalar_t& secret_x, const scalar_t& secret_q, uint64_t secret_index, const scalar_t& pseudo_out_blinding_mask, uint64_t stake_amount, const scalar_t& stake_blinding_mask, + const scalar_t& secret_x, const scalar_t& secret_q, uint64_t secret_index, const crypto::scalar_t& stake_out_asset_id_blinding_mask, const scalar_t& pseudo_out_blinding_mask, uint64_t stake_amount, const scalar_t& stake_blinding_mask, zarcanum_proof& result, uint8_t* p_err = nullptr); - bool zarcanum_verify_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, + bool zarcanum_verify_proof(const hash& m, const hash& kernel_hash, const std::vector& ring, const scalar_t& last_pow_block_id_hashed, const key_image& stake_ki, const mp::uint128_t& pos_difficulty, - const zarcanum_proof& sig, uint8_t* p_err = nullptr); + const zarcanum_proof& sig, uint8_t* p_err = nullptr) noexcept; + + + // TODO @#@#: make sure it is used, implement, then move it to an appropriate place + struct linear_composition_proof + { + scalar_t c; + scalar_t y0; + scalar_t y1; + }; + + enum generator_tag { gt_void = 0, gt_G = 1, gt_H = 2, gt_H2 = 3, gt_X = 4, gt_U = 5 }; + + template + bool generate_linear_composition_proof(const hash& m, const public_key& A, const scalar_t& secret_a, const scalar_t& secret_b, linear_composition_proof& result, uint8_t* p_err = nullptr) + { + // consider embedding generators' tags into random entropy to distinguish proofs made with different generators during verification + return false; + } + + template + bool verify_linear_composition_proof(const hash& m, const public_key& A, const linear_composition_proof& sig, uint8_t* p_err = nullptr) + { + return false; + } + + + struct generic_schnorr_sig + { + scalar_t c; + scalar_t y; + }; + + template + inline bool generate_schnorr_sig(const hash& m, const point_t& A, const scalar_t& secret_a, generic_schnorr_sig& result); + + template<> + inline bool generate_schnorr_sig(const hash& m, const point_t& A, const scalar_t& secret_a, generic_schnorr_sig& result) + { +#ifndef NDEBUG + if (A != secret_a * c_point_G) + return false; +#endif + scalar_t r = scalar_t::random(); + point_t R = r * c_point_G; + hash_helper_t::hs_t hsc(3); + hsc.add_hash(m); + hsc.add_point(A); + hsc.add_point(R); + result.c = hsc.calc_hash(); + result.y.assign_mulsub(result.c, secret_a, r); // y = r - c * secret_a + return true; + } + + template<> + inline bool generate_schnorr_sig(const hash& m, const point_t& A, const scalar_t& secret_a, generic_schnorr_sig& result) + { +#ifndef NDEBUG + if (A != secret_a * c_point_X) + return false; +#endif + scalar_t r = scalar_t::random(); + point_t R = r * c_point_X; + hash_helper_t::hs_t hsc(3); + hsc.add_hash(m); + hsc.add_point(A); + hsc.add_point(R); + result.c = hsc.calc_hash(); + result.y.assign_mulsub(result.c, secret_a, r); // y = r - c * secret_a + return true; + } + + template + inline bool verify_schnorr_sig(const hash& m, const public_key& A, const generic_schnorr_sig& sig) noexcept; + + template<> + inline bool verify_schnorr_sig(const hash& m, const public_key& A, const generic_schnorr_sig& sig) noexcept + { + try + { + if (!sig.c.is_reduced() || !sig.y.is_reduced()) + return false; + hash_helper_t::hs_t hsc(3); + hsc.add_hash(m); + hsc.add_pub_key(A); + hsc.add_point(point_t(A).mul_plus_G(sig.c, sig.y)); // sig.y * G + sig.c * A + return sig.c == hsc.calc_hash(); + } + catch(...) + { + return false; + } + } + + template<> + inline bool verify_schnorr_sig(const hash& m, const public_key& A, const generic_schnorr_sig& sig) noexcept + { + try + { + if (!sig.c.is_reduced() || !sig.y.is_reduced()) + return false; + hash_helper_t::hs_t hsc(3); + hsc.add_hash(m); + hsc.add_pub_key(A); + hsc.add_point(sig.y * c_point_X + sig.c * point_t(A)); + return sig.c == hsc.calc_hash(); + } + catch(...) + { + return false; + } + } + + + // TODO: improve this proof using random weightning factor + struct vector_UG_aggregation_proof + { + std::vector amount_commitments_for_rp_aggregation; // E' = e * U + y' * G, premultiplied by 1/8 + scalar_vec_t y0s; + scalar_vec_t y1s; + scalar_t c; // common challenge + }; + + bool generate_vector_UG_aggregation_proof(const hash& m, const scalar_vec_t& u_secrets, const scalar_vec_t& g_secrets0, const scalar_vec_t& g_secrets1, + const std::vector& amount_commitments, + const std::vector& amount_commitments_for_rp_aggregation, + const std::vector& blinded_asset_ids, + vector_UG_aggregation_proof& result, uint8_t* p_err = nullptr); + + bool verify_vector_UG_aggregation_proof(const hash& m, const std::vector amount_commitments_1div8, const std::vector blinded_asset_ids_1div8, + const vector_UG_aggregation_proof& sig, uint8_t* p_err = nullptr) noexcept; + } // namespace crypto diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 589f7d22..be011871 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -1322,21 +1322,16 @@ bool blockchain_storage::prevalidate_miner_transaction(const block& b, uint64_t if (is_hardfork_active(ZANO_HARDFORK_04_ZARCANUM)) // TODO @#@# consider moving to validate_tx_for_hardfork_specific_terms { - if (pos) - { - CHECK_AND_ASSERT_MES(b.miner_tx.attachment.size() == 1, false, "coinbase transaction has incorrect number of attachments (" << b.miner_tx.attachment.size() << "), expected 2"); - CHECK_AND_ASSERT_MES(b.miner_tx.attachment[0].type() == typeid(zc_outs_range_proof), false, "coinbase transaction wrong attachment #0 type (expected: zc_outs_range_proof)"); - } - else - { - CHECK_AND_ASSERT_MES(b.miner_tx.attachment.size() == 2, false, "coinbase transaction has incorrect number of attachments (" << b.miner_tx.attachment.size() << "), expected 2"); - CHECK_AND_ASSERT_MES(b.miner_tx.attachment[0].type() == typeid(zc_outs_range_proof), false, "coinbase transaction wrong attachment #0 type (expected: zc_outs_range_proof)"); - CHECK_AND_ASSERT_MES(b.miner_tx.attachment[1].type() == typeid(zc_balance_proof), false, "coinbase transaction wrong attachmenttype #1 (expected: zc_balance_proof)"); - } + CHECK_AND_ASSERT_MES(b.miner_tx.attachment.empty(), false, "coinbase transaction has attachments; attachments are not allowed for coinbase transactions."); + CHECK_AND_ASSERT_MES(b.miner_tx.proofs.size() == 3, false, "coinbase transaction has incorrect number of proofs (" << b.miner_tx.proofs.size() << "), expected 2"); + CHECK_AND_ASSERT_MES(b.miner_tx.proofs[0].type() == typeid(zc_asset_surjection_proof), false, "coinbase transaction has incorrect type of proof #0 (expected: zc_asset_surjection_proof)"); + CHECK_AND_ASSERT_MES(b.miner_tx.proofs[1].type() == typeid(zc_outs_range_proof), false, "coinbase transaction has incorrect type of proof #1 (expected: zc_outs_range_proof)"); + CHECK_AND_ASSERT_MES(b.miner_tx.proofs[2].type() == typeid(zc_balance_proof), false, "coinbase transaction has incorrect type of proof #2 (expected: zc_balance_proof)"); } else { CHECK_AND_ASSERT_MES(b.miner_tx.attachment.empty(), false, "coinbase transaction has attachments; attachments are not allowed for coinbase transactions."); + CHECK_AND_ASSERT_MES(b.miner_tx.proofs.size() == 0, false, "pre-HF4 coinbase shoudn't have non-empty proofs containter"); } return true; @@ -1359,9 +1354,12 @@ bool blockchain_storage::validate_miner_transaction(const block& b, return false; } - if (!check_tx_balance(b.miner_tx, base_reward + fee)) + uint64_t block_reward = base_reward + fee; + + crypto::hash tx_id_for_post_hf4_era = b.miner_tx.version > TRANSACTION_VERSION_PRE_HF4 ? get_transaction_hash(b.miner_tx) : null_hash; + if (!check_tx_balance(b.miner_tx, tx_id_for_post_hf4_era, block_reward)) { - LOG_ERROR("coinbase transaction balance check failed. Block reward is " << print_money_brief(base_reward + fee) << "(" << print_money(base_reward) << "+" << print_money(fee) + LOG_ERROR("coinbase transaction balance check failed. Block reward is " << print_money_brief(block_reward) << "(" << print_money(base_reward) << "+" << print_money(fee) << ", blocks_size_median = " << blocks_size_median << ", cumulative_block_size = " << cumulative_block_size << ", fee = " << fee @@ -1371,6 +1369,15 @@ bool blockchain_storage::validate_miner_transaction(const block& b, return false; } + if (b.miner_tx.version > TRANSACTION_VERSION_PRE_HF4) + { + if (!verify_asset_surjection_proof(b.miner_tx, tx_id_for_post_hf4_era)) + { + LOG_ERROR("asset surjection proof verification failed for miner tx"); + return false; + } + } + LOG_PRINT_MAGENTA("Mining tx verification ok, blocks_size_median = " << blocks_size_median, LOG_LEVEL_2); return true; } @@ -1425,7 +1432,7 @@ bool blockchain_storage::create_block_template(const account_public_address& min block& b, wide_difficulty_type& diffic, uint64_t& height, - crypto::scalar_t* blinding_mask_sum_ptr /* = nullptr */) const + outputs_generation_context* miner_tx_ogc_ptr /* = nullptr */) const { create_block_template_params params = AUTO_VAL_INIT(params); params.miner_address = miner_address; @@ -1440,8 +1447,8 @@ bool blockchain_storage::create_block_template(const account_public_address& min b = resp.b; diffic = resp.diffic; height = resp.height; - if (blinding_mask_sum_ptr) - *blinding_mask_sum_ptr = resp.blinding_mask_sum; + if (miner_tx_ogc_ptr) + *miner_tx_ogc_ptr = resp.miner_tx_ogc; return r; } @@ -1526,7 +1533,7 @@ bool blockchain_storage::create_block_template(const create_block_template_param CURRENCY_MINER_TX_MAX_OUTS, pos, pe, - &resp.blinding_mask_sum); + &resp.miner_tx_ogc); CHECK_AND_ASSERT_MES(r, false, "Failed to construc miner tx, first chance"); uint64_t coinbase_size = get_object_blobsize(b.miner_tx); // "- 100" - to reserve room for PoS additions into miner tx @@ -2570,6 +2577,7 @@ bool blockchain_storage::add_out_to_get_random_outs(COMMAND_RPC_GET_RANDOM_OUTPU oen.stealth_address = toz.stealth_address; oen.amount_commitment = toz.amount_commitment; oen.concealing_point = toz.concealing_point; + oen.blinded_asset_id = toz.blinded_asset_id; // TODO @#@# bad design, too much manual coping, consider redesign -- sowle } VARIANT_SWITCH_END(); @@ -3544,15 +3552,14 @@ bool blockchain_storage::unprocess_blockchain_tx_extra(const transaction& tx) if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) { - crypto::hash asset_id = currency::null_hash; + crypto::public_key asset_id = currency::null_pkey; if (ei.m_asset_operation.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { - asset_id = get_asset_id_from_descriptor(ei.m_asset_operation.descriptor); + calculate_asset_id(ei.m_asset_operation.descriptor.owner, nullptr, &asset_id); } else { - CHECK_AND_ASSERT_MES(ei.m_asset_operation.asset_id.size() == 1, false, "Unexpected asset_id in operation"); - asset_id = ei.m_asset_operation.asset_id.back(); + CHECK_AND_NO_ASSERT_MES(false, false, "asset operation not implemented"); } r = pop_asset_info(asset_id); CHECK_AND_ASSERT_MES(r, false, "failed to pop_alias_info"); @@ -3581,7 +3588,7 @@ uint64_t blockchain_storage::get_aliases_count() const return m_db_aliases.size(); } //------------------------------------------------------------------ -bool blockchain_storage::get_asset_info(const crypto::hash& asset_id, asset_descriptor_base& info)const +bool blockchain_storage::get_asset_info(const crypto::public_key& asset_id, asset_descriptor_base& result) const { CRITICAL_REGION_LOCAL(m_read_lock); auto as_ptr = m_db_assets.find(asset_id); @@ -3589,7 +3596,7 @@ bool blockchain_storage::get_asset_info(const crypto::hash& asset_id, asset_desc { if (as_ptr->size()) { - info = as_ptr->back().descriptor; + result = as_ptr->back().descriptor; return true; } } @@ -3784,7 +3791,7 @@ bool blockchain_storage::put_alias_info(const transaction & tx, extra_alias_entr return true; } //------------------------------------------------------------------ -bool blockchain_storage::pop_asset_info(const crypto::hash& asset_id) +bool blockchain_storage::pop_asset_info(const crypto::public_key& asset_id) { CRITICAL_REGION_LOCAL(m_read_lock); @@ -3802,14 +3809,16 @@ bool blockchain_storage::pop_asset_info(const crypto::hash& asset_id) return true; } //------------------------------------------------------------------ -bool blockchain_storage::put_asset_info(const transaction & tx, asset_descriptor_operation & ado) +bool blockchain_storage::put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado) { CRITICAL_REGION_LOCAL(m_read_lock); if (ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) { - crypto::hash asset_id = get_asset_id_from_descriptor(ado.descriptor); + crypto::public_key asset_id{}; + CHECK_AND_ASSERT_MES(validate_asset_operation(tx, tx_id, ado, asset_id), false, "asset operation validation failed!"); + auto asset_history_ptr = m_db_assets.find(asset_id); - CHECK_AND_ASSERT_MES(!asset_history_ptr, false, "Asset id already existing"); + CHECK_AND_ASSERT_MES(!asset_history_ptr, false, "asset with id " << asset_id << " has already been registered"); assets_container::t_value_type local_asset_history = AUTO_VAL_INIT(local_asset_history); local_asset_history.push_back(ado); m_db_assets.set(asset_id, local_asset_history); @@ -3820,7 +3829,7 @@ bool blockchain_storage::put_asset_info(const transaction & tx, asset_descriptor else { //TODO: implement other operations - CHECK_AND_ASSERT_THROW(false, "not implemented yet"); + CHECK_AND_ASSERT_THROW(false, "asset operation not implemented yet"); } return true; @@ -3901,7 +3910,7 @@ bool blockchain_storage::prevalidate_alias_info(const transaction& tx, const ext return true; } //------------------------------------------------------------------ -bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx) +bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id) { //check transaction extra tx_extra_info ei = AUTO_VAL_INIT(ei); @@ -3917,7 +3926,7 @@ bool blockchain_storage::process_blockchain_tx_extra(const transaction& tx) } if (ei.m_asset_operation.operation_type != ASSET_DESCRIPTOR_OPERATION_UNDEFINED) { - r = put_asset_info(tx, ei.m_asset_operation); + r = put_asset_info(tx, tx_id, ei.m_asset_operation); CHECK_AND_ASSERT_MES(r, false, "failed to put_asset_info"); } @@ -4095,7 +4104,7 @@ bool blockchain_storage::add_transaction_from_block(const transaction& tx, const CHECK_AND_ASSERT_MES(validate_tx_for_hardfork_specific_terms(tx, tx_id, bl_height), false, "tx " << tx_id << ": hardfork-specific validation failed"); TIME_MEASURE_START_PD(tx_process_extra); - bool r = process_blockchain_tx_extra(tx); + bool r = process_blockchain_tx_extra(tx, tx_id); CHECK_AND_ASSERT_MES(r, false, "failed to process_blockchain_tx_extra"); TIME_MEASURE_FINISH_PD_COND(need_to_profile, tx_process_extra); @@ -4416,6 +4425,34 @@ bool blockchain_storage::print_tx_outputs_lookup(const crypto::hash& tx_id)const return true; } //------------------------------------------------------------------ +bool check_tx_explicit_asset_id_rules(const transaction& tx, bool all_tx_ins_have_explicit_asset_ids) +{ + if (tx.version <= TRANSACTION_VERSION_PRE_HF4) + return true; + + // ( assuming that post-HF4 txs can only have tx_out_zarcanum outs ) + + bool r = false; + // if all tx inputs have explicit asset id AND it does not emit a new asset THEN all outputs must have explicit asset id (native coin) + if (all_tx_ins_have_explicit_asset_ids && !is_asset_emitting_transaction(tx)) + { + for(size_t j = 0, k = tx.vout.size(); j < k; ++j) + { + r = crypto::point_t(boost::get(tx.vout[j]).blinded_asset_id).modify_mul8().to_public_key() == native_coin_asset_id; + CHECK_AND_ASSERT_MES(r, false, "output #" << j << " has a non-explicit asset id"); + } + } + else // otherwise all outputs must have hidden asset id + { + for(size_t j = 0, k = tx.vout.size(); j < k; ++j) + { + r = crypto::point_t(boost::get(tx.vout[j]).blinded_asset_id).modify_mul8().to_public_key() != native_coin_asset_id; + CHECK_AND_ASSERT_MES(r, false, "output #" << j << " has an explicit asset id"); + } + } + return true; +} +//------------------------------------------------------------------ bool blockchain_storage::have_tx_keyimges_as_spent(const transaction &tx) const { // check all tx's inputs for being already spent @@ -4456,6 +4493,7 @@ bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::ha { size_t sig_index = 0; max_used_block_height = 0; + bool all_tx_ins_have_explicit_asset_ids = true; auto local_check_key_image = [&](const crypto::key_image& ki) -> bool { @@ -4517,7 +4555,7 @@ bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::ha if (!local_check_key_image(in_zc.k_image)) return false; - if (!check_tx_input(tx, sig_index, in_zc, tx_prefix_hash, max_used_block_height)) + if (!check_tx_input(tx, sig_index, in_zc, tx_prefix_hash, max_used_block_height, all_tx_ins_have_explicit_asset_ids)) { LOG_ERROR("Failed to validate zc input #" << sig_index << " in tx: " << tx_prefix_hash); return false; @@ -4539,6 +4577,8 @@ bool blockchain_storage::check_tx_inputs(const transaction& tx, const crypto::ha bool r = validate_attachment_info(tx.extra, tx.attachment, false); CHECK_AND_ASSERT_MES(r, false, "Failed to validate attachments in tx " << tx_prefix_hash << ": incorrect extra_attachment_info in tx.extra"); } + + CHECK_AND_ASSERT_MES(check_tx_explicit_asset_id_rules(tx, all_tx_ins_have_explicit_asset_ids), false, "tx does not comply with explicit asset id rules"); } TIME_MEASURE_FINISH_PD(tx_check_inputs_attachment_check); return true; @@ -4920,7 +4960,8 @@ bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, return check_input_signature(tx, in_index, txin.amount, txin.k_image, txin.etc_details, tx_prefix_hash, output_keys_ptrs); } //------------------------------------------------------------------ -bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, const txin_zc_input& zc_in, const crypto::hash& tx_prefix_hash, uint64_t& max_related_block_height) const +bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, const txin_zc_input& zc_in, const crypto::hash& tx_prefix_hash, + uint64_t& max_related_block_height, bool& all_tx_ins_have_explicit_asset_ids) const { CRITICAL_REGION_LOCAL(m_read_lock); @@ -4938,7 +4979,8 @@ bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, return false; } - // @#@ + if (m_is_in_checkpoint_zone) // TODO @#@# reconsider placement of this check + return true; // here we don't need to check zc_in.k_image validity because it is checked in verify_CLSAG_GG() @@ -4947,10 +4989,14 @@ bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, // TODO: consider additional checks here // build a ring of references - vector ring; + vector ring; ring.reserve(scan_contex.zc_outs.size()); for(auto& zc_out : scan_contex.zc_outs) - ring.emplace_back(zc_out.stealth_address, zc_out.amount_commitment); + { + ring.emplace_back(zc_out.stealth_address, zc_out.amount_commitment, zc_out.blinded_asset_id); + if (all_tx_ins_have_explicit_asset_ids && crypto::point_t(zc_out.blinded_asset_id).modify_mul8().to_public_key() != native_coin_asset_id) + all_tx_ins_have_explicit_asset_ids = false; + } // calculate corresponding tx prefix hash crypto::hash tx_hash_for_signature = prepare_prefix_hash_for_sign(tx, in_index, tx_prefix_hash); @@ -4958,12 +5004,12 @@ bool blockchain_storage::check_tx_input(const transaction& tx, size_t in_index, const ZC_sig& sig = boost::get(tx.signatures[in_index]); - //TIME_MEASURE_START_PD(tx_input_check_clsag_gg); + //TIME_MEASURE_START_PD(tx_input_check_clsag_ggx); - bool r = crypto::verify_CLSAG_GG(tx_hash_for_signature, ring, sig.pseudo_out_amount_commitment, zc_in.k_image, sig.clsags_gg); - CHECK_AND_ASSERT_MES(r, false, "verify_CLSAG_GG failed"); + bool r = crypto::verify_CLSAG_GGX(tx_hash_for_signature, ring, sig.pseudo_out_amount_commitment, sig.pseudo_out_blinded_asset_id, zc_in.k_image, sig.clsags_ggx); + CHECK_AND_ASSERT_MES(r, false, "verify_CLSAG_GGX failed"); - //TIME_MEASURE_FINISH_PD(tx_input_check_clsag_gg); + //TIME_MEASURE_FINISH_PD(tx_input_check_clsag_ggx); return true; } @@ -5350,6 +5396,7 @@ bool blockchain_storage::validate_tx_for_hardfork_specific_terms(const transacti return false; } + // TODO @#@# consider: 1) tx.proofs, 2) new proof data structures if (var_is_after_hardfork_4_zone) { @@ -5481,10 +5528,10 @@ bool blockchain_storage::validate_pos_block(const block& b, CHECK_AND_ASSERT_MES(max_related_block_height <= last_pow_block_height, false, "stake input refs' max related block height is " << max_related_block_height << " while last PoW block height is " << last_pow_block_height); // build a ring of references - vector ring; + vector ring; ring.reserve(scan_contex.zc_outs.size()); for(auto& zc_out : scan_contex.zc_outs) - ring.emplace_back(zc_out.stealth_address, zc_out.amount_commitment, zc_out.concealing_point); + ring.emplace_back(zc_out.stealth_address, zc_out.amount_commitment, zc_out.blinded_asset_id, zc_out.concealing_point); crypto::scalar_t last_pow_block_id_hashed = crypto::hash_helper_t::hs(CRYPTO_HDS_ZARCANUM_LAST_POW_HASH, sm.last_pow_id); @@ -5703,35 +5750,47 @@ bool get_tx_from_cache(const crypto::hash& tx_id, transactions_map& tx_cache, tr return true; } //------------------------------------------------------------------ -bool blockchain_storage::collect_rangeproofs_data_from_tx(std::vector& agregated_proofs, const transaction& tx) +bool blockchain_storage::collect_rangeproofs_data_from_tx(const transaction& tx, const crypto::hash& tx_id, std::vector& agregated_proofs) { if (tx.version <= TRANSACTION_VERSION_PRE_HF4) - { return true; - } - //@#@ Verify somewhere(maybe here) that all outputs are covered with associated rangeproofs - size_t proofs_count = 0; - size_t current_output_start = 0; //for Consolidated Transactions we'll have multiple zc_outs_range_proof entries - for (const auto& a : tx.attachment) + size_t range_proofs_count = 0; + size_t out_index_offset = 0; //Consolidated Transactions have multiple zc_outs_range_proof entries + for (const auto& a : tx.proofs) { if (a.type() == typeid(zc_outs_range_proof)) { const zc_outs_range_proof& zcrp = boost::get(a); - agregated_proofs.emplace_back(zcrp); - for (uint8_t i = 0; i != zcrp.outputs_count; i++) + + // validate aggregation proof + std::vector amount_commitment_ptrs_1div8, blinded_asset_id_ptrs_1div8; + for(size_t j = out_index_offset; j < tx.vout.size(); ++j) { - CHECK_AND_ASSERT_MES(tx.vout[i + current_output_start].type() == typeid(tx_out_zarcanum), false, "Unexpected type of out in collect_rangeproofs_data_from_tx()"); - const tx_out_zarcanum& zc_out = boost::get(tx.vout[i + current_output_start]); - agregated_proofs.back().amount_commitments.emplace_back(zc_out.amount_commitment); + CHECKED_GET_SPECIFIC_VARIANT(tx.vout[j], const tx_out_zarcanum, zcout, false); + amount_commitment_ptrs_1div8.push_back(&zcout.amount_commitment); + blinded_asset_id_ptrs_1div8.push_back(&zcout.blinded_asset_id); } - current_output_start += zcrp.outputs_count; - proofs_count++; + uint8_t err = 0; + bool r = crypto::verify_vector_UG_aggregation_proof(tx_id, amount_commitment_ptrs_1div8, blinded_asset_id_ptrs_1div8, zcrp.aggregation_proof, &err); + CHECK_AND_ASSERT_MES(r, false, "verify_vector_UG_aggregation_proof failed with err code " << (int)err); + + + agregated_proofs.emplace_back(zcrp); + + // convert amount commitments for aggregation from public_key to point_t form + // TODO: consider refactoring this ugly code + for (uint8_t i = 0; i != zcrp.aggregation_proof.amount_commitments_for_rp_aggregation.size(); i++) + agregated_proofs.back().amount_commitments.emplace_back(zcrp.aggregation_proof.amount_commitments_for_rp_aggregation[i]); + + out_index_offset += zcrp.aggregation_proof.amount_commitments_for_rp_aggregation.size(); + range_proofs_count++; } } - CHECK_AND_ASSERT_MES(proofs_count > 0, false, "transaction " << get_transaction_hash(tx) << " don't have range_proofs"); - CHECK_AND_ASSERT_MES(proofs_count == 1 || (get_tx_flags(tx) & TX_FLAG_SIGNATURE_MODE_SEPARATE), false, "transaction " << get_transaction_hash(tx) - << " has TX_FLAG_SIGNATURE_MODE_SEPARATE but proofs_count = " << proofs_count); + CHECK_AND_ASSERT_MES(out_index_offset == tx.vout.size(), false, "range proof elements count doesn't match with outputs count: " << out_index_offset << " != " << tx.vout.size()); + CHECK_AND_ASSERT_MES(range_proofs_count > 0, false, "transaction " << get_transaction_hash(tx) << " doesn't have range proofs"); + CHECK_AND_ASSERT_MES(range_proofs_count == 1 || (get_tx_flags(tx) & TX_FLAG_SIGNATURE_MODE_SEPARATE), false, "transaction " << get_transaction_hash(tx) + << " doesn't have TX_FLAG_SIGNATURE_MODE_SEPARATE but has range_proofs_count = " << range_proofs_count); return true; } @@ -5885,24 +5944,27 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt append_per_block_increments_for_tx(tx, gindices); - //If we under checkpoints, ring signatures should be pruned + //If we under checkpoints, attachments, ring signatures and proofs should be pruned if(m_is_in_checkpoint_zone) { - tx.signatures.clear(); tx.attachment.clear(); + tx.signatures.clear(); + tx.proofs.clear(); } //std::vector tx_outs_commitments; if (!m_is_in_checkpoint_zone) { - if (!collect_rangeproofs_data_from_tx(range_proofs_agregated, tx/*, tx_outs_commitments*/)) - { - LOG_PRINT_L0("Block with id: " << id << " has at least one transaction with wrong proofs, tx_id: " << tx_id << ", collect_rangeproofs_data_from_tx failed"); - purge_block_data_from_blockchain(bl, tx_processed_count); - //add_block_as_invalid(bl, id); - bvc.m_verification_failed = true; - return false; - } + auto cleanup = [&](){ purge_block_data_from_blockchain(bl, tx_processed_count); bvc.m_verification_failed = true; }; + + CHECK_AND_ASSERT_MES_CUSTOM(collect_rangeproofs_data_from_tx(tx, tx_id, range_proofs_agregated), false, cleanup(), + "block " << id << ", tx " << tx_id << ": collect_rangeproofs_data_from_tx failed"); + + CHECK_AND_ASSERT_MES_CUSTOM(check_tx_balance(tx, tx_id), false, cleanup(), + "block " << id << ", tx " << tx_id << ": check_tx_balance failed"); + + CHECK_AND_ASSERT_MES_CUSTOM(verify_asset_surjection_proof(tx, tx_id), false, cleanup(), + "block " << id << ", tx " << tx_id << ": verify_asset_surjection_proof failed"); } TIME_MEASURE_START_PD(tx_add_one_tx_time); @@ -5969,9 +6031,21 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt return false; } + boost::multiprecision::uint128_t already_generated_coins = m_db_blocks.size() ? m_db_blocks.back()->already_generated_coins:0; + uint64_t base_reward = get_base_block_reward(is_pos_bl, already_generated_coins, height); + if (!m_is_in_checkpoint_zone) { - if (!collect_rangeproofs_data_from_tx(range_proofs_agregated, bl.miner_tx)) + if (!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins)) // TODO @#@# base_reward will be calculated once again, consider refactoring + { + LOG_PRINT_L0("Block with id: " << id + << " have wrong miner transaction"); + purge_block_data_from_blockchain(bl, tx_processed_count); + bvc.m_verification_failed = true; + return false; + } + + if (!collect_rangeproofs_data_from_tx(bl.miner_tx, get_transaction_hash(bl.miner_tx), range_proofs_agregated)) { LOG_PRINT_L0("Block with id: " << id << " have wrong miner tx, failed to collect_rangeproofs_data_from_tx()"); @@ -5979,28 +6053,16 @@ bool blockchain_storage::handle_block_to_main_chain(const block& bl, const crypt bvc.m_verification_failed = true; return false; } - } - uint64_t base_reward = 0; - boost::multiprecision::uint128_t already_generated_coins = m_db_blocks.size() ? m_db_blocks.back()->already_generated_coins:0; - if (!validate_miner_transaction(bl, cumulative_block_size, fee_summary, base_reward, already_generated_coins)) - { - LOG_PRINT_L0("Block with id: " << id - << " have wrong miner transaction"); - purge_block_data_from_blockchain(bl, tx_processed_count); - bvc.m_verification_failed = true; - return false; - } - - - //validate range proofs - if (!verify_multiple_zc_outs_range_proofs(range_proofs_agregated)) - { - LOG_PRINT_L0("Block with id: " << id - << " have failed to verify multiple rangeproofs"); - purge_block_data_from_blockchain(bl, tx_processed_count); - bvc.m_verification_failed = true; - return false; + //validate range proofs + if (!verify_multiple_zc_outs_range_proofs(range_proofs_agregated)) + { + LOG_PRINT_L0("Block with id: " << id + << " have failed to verify multiple rangeproofs"); + purge_block_data_from_blockchain(bl, tx_processed_count); + bvc.m_verification_failed = true; + return false; + } } @@ -7010,7 +7072,8 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, else { uint64_t max_related_block_height = 0; - r = check_tx_input(input_tx, input_index, input_zc, input_tx_hash, max_related_block_height); + bool all_tx_ins_have_explicit_asset_ids = true; // stub for now, TODO @#@# + r = check_tx_input(input_tx, input_index, input_zc, input_tx_hash, max_related_block_height, all_tx_ins_have_explicit_asset_ids); CHECK_AND_ASSERT_MES(r, false, "check_tx_input failed"); } VARIANT_CASE_OTHER() diff --git a/src/currency_core/blockchain_storage.h b/src/currency_core/blockchain_storage.h index 7537919d..d7d46185 100644 --- a/src/currency_core/blockchain_storage.h +++ b/src/currency_core/blockchain_storage.h @@ -261,7 +261,7 @@ namespace currency bool create_block_template(const account_public_address& miner_address, const blobdata& ex_nonce, block& b, wide_difficulty_type& di, uint64_t& height) const; - bool create_block_template(const account_public_address& miner_address, const account_public_address& stakeholder_address, const blobdata& ex_nonce, bool pos, const pos_entry& pe, fill_block_template_func_t custom_fill_block_template_func, block& b, wide_difficulty_type& di, uint64_t& height, crypto::scalar_t* blinding_mask_sum_ptr = nullptr) const; + bool create_block_template(const account_public_address& miner_address, const account_public_address& stakeholder_address, const blobdata& ex_nonce, bool pos, const pos_entry& pe, fill_block_template_func_t custom_fill_block_template_func, block& b, wide_difficulty_type& di, uint64_t& height, outputs_generation_context* miner_tx_ogc_ptr = nullptr) const; bool create_block_template(const create_block_template_params& params, create_block_template_response& resp) const; bool have_block(const crypto::hash& id) const; @@ -288,12 +288,12 @@ namespace currency uint64_t get_aliases_count()const; uint64_t get_block_h_older_then(uint64_t timestamp) const; bool validate_tx_service_attachmens_in_services(const tx_service_attachment& a, size_t i, const transaction& tx)const; - bool get_asset_info(const crypto::hash& asset_id, asset_descriptor_base& info)const; + bool get_asset_info(const crypto::public_key& asset_id, asset_descriptor_base& info)const; uint64_t get_assets_count() const; bool check_tx_input(const transaction& tx, size_t in_index, const txin_to_key& txin, const crypto::hash& tx_prefix_hash, uint64_t& max_related_block_height, uint64_t& source_max_unlock_time_for_pos_coinbase)const; bool check_tx_input(const transaction& tx, size_t in_index, const txin_multisig& txin, const crypto::hash& tx_prefix_hash, uint64_t& max_related_block_height)const; bool check_tx_input(const transaction& tx, size_t in_index, const txin_htlc& txin, const crypto::hash& tx_prefix_hash, uint64_t& max_related_block_height)const; - bool check_tx_input(const transaction& tx, size_t in_index, const txin_zc_input& zc_in, const crypto::hash& tx_prefix_hash, uint64_t& max_related_block_height) const; + bool check_tx_input(const transaction& tx, size_t in_index, const txin_zc_input& zc_in, const crypto::hash& tx_prefix_hash, uint64_t& max_related_block_height, bool& tx_has_explicit_asset_ids_in_all_ins) const; bool check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t& max_used_block_height)const; bool check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash) const; bool check_tx_inputs(const transaction& tx, const crypto::hash& tx_prefix_hash, uint64_t& max_used_block_height, crypto::hash& max_used_block_id)const; @@ -492,7 +492,7 @@ namespace currency typedef tools::db::cached_key_value_accessor solo_options_container; typedef tools::db::basic_key_value_accessor per_block_gindex_increments_container; // height => [(amount, gindex_increment), ...] - typedef tools::db::cached_key_value_accessor, true, false> assets_container; + typedef tools::db::cached_key_value_accessor, true, false> assets_container; // TODO @#@# consider storing tx_id as well for reference -- sowle //----------------------------------------- @@ -593,7 +593,7 @@ namespace currency wide_difficulty_type get_next_difficulty_for_alternative_chain(const alt_chain_type& alt_chain, block_extended_info& bei, bool pos) const; bool handle_block_to_main_chain(const block& bl, block_verification_context& bvc); bool handle_block_to_main_chain(const block& bl, const crypto::hash& id, block_verification_context& bvc); - bool collect_rangeproofs_data_from_tx(std::vector& agregated_proofs, const transaction& tx); + bool collect_rangeproofs_data_from_tx(const transaction& tx, const crypto::hash& tx_id, std::vector& agregated_proofs); std::string print_alt_chain(alt_chain_type alt_chain); bool handle_alternative_block(const block& b, const crypto::hash& id, block_verification_context& bvc); bool is_reorganize_required(const block_extended_info& main_chain_bei, const alt_chain_type& alt_chain, const crypto::hash& proof_alt); @@ -648,14 +648,14 @@ namespace currency uint64_t get_adjusted_time()const; bool complete_timestamps_vector(uint64_t start_height, std::vector& timestamps); bool update_next_comulative_size_limit(); - bool process_blockchain_tx_extra(const transaction& tx); + bool process_blockchain_tx_extra(const transaction& tx, const crypto::hash& tx_id); bool unprocess_blockchain_tx_extra(const transaction& tx); bool process_blockchain_tx_attachments(const transaction& tx, uint64_t h, const crypto::hash& bl_id, uint64_t timestamp); bool unprocess_blockchain_tx_attachments(const transaction& tx, uint64_t h, uint64_t timestamp); bool pop_alias_info(const extra_alias_entry& ai); bool put_alias_info(const transaction& tx, extra_alias_entry& ai); - bool pop_asset_info(const crypto::hash& asset_id); - bool put_asset_info(const transaction & tx, asset_descriptor_operation & ado); + bool pop_asset_info(const crypto::public_key& asset_id); + bool put_asset_info(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado); void fill_addr_to_alias_dict(); //bool resync_spent_tx_flags(); bool prune_ring_signatures_and_attachments_if_need(); diff --git a/src/currency_core/blockchain_storage_basic.h b/src/currency_core/blockchain_storage_basic.h index c0a1a181..72170005 100644 --- a/src/currency_core/blockchain_storage_basic.h +++ b/src/currency_core/blockchain_storage_basic.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2023 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Copyright (c) 2012-2013 The Boolberry developers @@ -19,6 +19,8 @@ #include "currency_basic.h" #include "difficulty.h" #include "currency_protocol/blobdatatype.h" +#include "currency_format_utils_transactions.h" // only for output_generation_context + namespace currency { @@ -144,7 +146,7 @@ namespace currency block b; wide_difficulty_type diffic; uint64_t height; - crypto::scalar_t blinding_mask_sum; // sum of all the outputs' blinding masks + outputs_generation_context miner_tx_ogc; // bad design, a lot of copying, consider redesign -- sowle }; typedef std::unordered_map transactions_map; @@ -157,4 +159,4 @@ namespace currency -} \ No newline at end of file +} // namespace currency diff --git a/src/currency_core/crypto_config.h b/src/currency_core/crypto_config.h index 087c1cfd..80a92224 100644 --- a/src/currency_core/crypto_config.h +++ b/src/currency_core/crypto_config.h @@ -1,4 +1,4 @@ -// Copyright (c) 2022 Zano Project +// Copyright (c) 2022-2023 Zano Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. // @@ -8,21 +8,35 @@ // #define CRYPTO_HDS_OUT_AMOUNT_MASK "ZANO_HDS_OUT_AMOUNT_MASK_______" -#define CRYPTO_HDS_OUT_BLINDING_MASK "ZANO_HDS_OUT_BLINDING_MASK_____" +#define CRYPTO_HDS_OUT_AMOUNT_BLINDING_MASK "ZANO_HDS_OUT_AMOUNT_BLIND_MASK_" +#define CRYPTO_HDS_OUT_ASSET_BLINDING_MASK "ZANO_HDS_OUT_ASSET_BLIND_MASK__" #define CRYPTO_HDS_OUT_CONCEALING_POINT "ZANO_HDS_OUT_CONCEALING_POINT__" #define CRYPTO_HDS_CLSAG_GG_LAYER_0 "ZANO_HDS_CLSAG_GG_LAYER_ZERO___" #define CRYPTO_HDS_CLSAG_GG_LAYER_1 "ZANO_HDS_CLSAG_GG_LAYER_ONE____" #define CRYPTO_HDS_CLSAG_GG_CHALLENGE "ZANO_HDS_CLSAG_GG_CHALLENGE____" +#define CRYPTO_HDS_CLSAG_GGX_LAYER_0 "ZANO_HDS_CLSAG_GGX_LAYER_ZERO__" +#define CRYPTO_HDS_CLSAG_GGX_LAYER_1 "ZANO_HDS_CLSAG_GGX_LAYER_ONE___" +#define CRYPTO_HDS_CLSAG_GGX_LAYER_2 "ZANO_HDS_CLSAG_GGX_LAYER_TWO___" +#define CRYPTO_HDS_CLSAG_GGX_CHALLENGE "ZANO_HDS_CLSAG_GGX_CHALLENGE___" + #define CRYPTO_HDS_CLSAG_GGXG_LAYER_0 "ZANO_HDS_CLSAG_GGXG_LAYER_ZERO_" #define CRYPTO_HDS_CLSAG_GGXG_LAYER_1 "ZANO_HDS_CLSAG_GGXG_LAYER_ONE__" #define CRYPTO_HDS_CLSAG_GGXG_LAYER_2 "ZANO_HDS_CLSAG_GGXG_LAYER_TWO__" #define CRYPTO_HDS_CLSAG_GGXG_LAYER_3 "ZANO_HDS_CLSAG_GGXG_LAYER_THREE" #define CRYPTO_HDS_CLSAG_GGXG_CHALLENGE "ZANO_HDS_CLSAG_GGXG_CHALLENGE__" +#define CRYPTO_HDS_CLSAG_GGXXG_LAYER_0 "ZANO_HDS_CLSAG_GGXXG_LAYER_ZERO" +#define CRYPTO_HDS_CLSAG_GGXXG_LAYER_1 "ZANO_HDS_CLSAG_GGXXG_LAYER_ONE_" +#define CRYPTO_HDS_CLSAG_GGXXG_LAYER_2 "ZANO_HDS_CLSAG_GGXXG_LAYER_TWO_" +#define CRYPTO_HDS_CLSAG_GGXXG_LAYER_3 "ZANO_HDS_CLSAG_GGXXG_LAYER_3___" +#define CRYPTO_HDS_CLSAG_GGXXG_LAYER_4 "ZANO_HDS_CLSAG_GGXXG_LAYER_FOUR" +#define CRYPTO_HDS_CLSAG_GGXXG_CHALLENGE "ZANO_HDS_CLSAG_GGXXG_CHALLENGE_" + #define CRYPTO_HDS_ZARCANUM_LAST_POW_HASH "ZANO_HDS_ZARCANUM_LAST_POW_HASH" #define CRYPTO_HDS_ZARCANUM_PROOF_HASH "ZANO_HDS_ZARCANUM_PROOF_HASH___" #define CRYPTO_HDS_ASSET_CONTROL_KEY "ZANO_HDS_ASSET_CONTROL_KEY_____" +#define CRYPTO_HDS_ASSET_CONTROL_ABM "ZANO_HDS_ASSET_CONTROL_ABM_____" #define CRYPTO_HDS_ASSET_ID "ZANO_HDS_ASSET_ID______________" diff --git a/src/currency_core/currency_basic.h b/src/currency_core/currency_basic.h index d79c5122..e64155c0 100644 --- a/src/currency_core/currency_basic.h +++ b/src/currency_core/currency_basic.h @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2019 Zano Project +// Copyright (c) 2014-2023 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Copyright (c) 2014-2015 The Boolberry developers @@ -30,6 +30,7 @@ #include "serialization/stl_containers.h" #include "serialization/serialization.h" #include "serialization/variant.h" +#include "serialization/boost_types.h" #include "serialization/json_archive.h" #include "serialization/debug_archive.h" #include "serialization/keyvalue_serialization.h" // epee key-value serialization @@ -56,6 +57,12 @@ namespace currency const static crypto::hash gdefault_genesis = epee::string_tools::hex_to_pod("CC608F59F8080E2FBFE3C8C80EB6E6A953D47CF2D6AEBD345BADA3A1CAB99852"); const static crypto::hash ffff_hash = epee::string_tools::hex_to_pod("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + const static crypto::public_key ffff_pkey = epee::string_tools::hex_to_pod("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); // TODO @#@# consider getting rid of this + + // TODO: rewtire the following to a normal aggregate initialization once we move to C++17 -- sowle + const static crypto::public_key native_coin_asset_id = reinterpret_cast(static_cast(crypto::ec_scalar{'\xd6', '\x32', '\x9b', '\x5b', '\x1f', '\x7c', '\x08', '\x05', '\xb5', '\xc3', '\x45', '\xf4', '\x95', '\x75', '\x54', '\x00', '\x2a', '\x2f', '\x55', '\x78', '\x45', '\xf6', '\x4d', '\x76', '\x45', '\xda', '\xe0', '\xe0', '\x51', '\xa6', '\x49', '\x8a'})); // == crypto::c_point_H, checked in crypto_constants + const static crypto::public_key native_coin_asset_id_1div8 = reinterpret_cast(static_cast(crypto::ec_scalar{'\x74', '\xc3', '\x2d', '\x3e', '\xaa', '\xfa', '\xfc', '\x62', '\x3b', '\xf4', '\x83', '\xe8', '\x58', '\xd4', '\x2e', '\x8b', '\xf4', '\xec', '\x7d', '\xf0', '\x64', '\xad', '\xa2', '\xe3', '\x49', '\x34', '\x46', '\x9c', '\xff', '\x6b', '\x62', '\x68'})); // == 1/8 * crypto::c_point_H, checked in crypto_constants + const static crypto::point_t native_coin_asset_id_pt = crypto::c_point_H; const static wide_difficulty_type global_difficulty_pow_starter = DIFFICULTY_POW_STARTER; const static wide_difficulty_type global_difficulty_pos_starter = DIFFICULTY_POS_STARTER; @@ -214,22 +221,8 @@ namespace currency uint32_t n_extras; }; - //!!!!this is temporary struct!!! - //needed only to hold asset_id of input/output while zarcanum extension being developed - struct open_asset_id - { - crypto::hash asset_id; - BEGIN_SERIALIZE_OBJECT() - FIELD(asset_id) // referring_input - END_SERIALIZE() - BEGIN_BOOST_SERIALIZATION() - BOOST_SERIALIZE(asset_id) - END_BOOST_SERIALIZATION() - }; - - - typedef boost::variant txin_etc_details_v; + typedef boost::variant txin_etc_details_v; struct referring_input @@ -394,8 +387,6 @@ namespace currency END_BOOST_SERIALIZATION() }; - typedef boost::variant txout_etc_details_v; - struct tx_out_zarcanum { tx_out_zarcanum() {} @@ -407,27 +398,26 @@ namespace currency crypto::public_key stealth_address; crypto::public_key concealing_point; // group element Q, see also Zarcanum paper, premultiplied by 1/8 crypto::public_key amount_commitment; // premultiplied by 1/8 - uint64_t encrypted_amount; - uint8_t mix_attr; - std::vector etc_details; - //crypto::public_key token_masked_generator; + crypto::public_key blinded_asset_id; // group element T, premultiplied by 1/8 + uint64_t encrypted_amount = 0; + uint8_t mix_attr = 0; BEGIN_SERIALIZE_OBJECT() FIELD(stealth_address) FIELD(concealing_point) FIELD(amount_commitment) + FIELD(blinded_asset_id) FIELD(encrypted_amount) FIELD(mix_attr) - FIELD(etc_details) END_SERIALIZE() BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(stealth_address) BOOST_SERIALIZE(concealing_point) BOOST_SERIALIZE(amount_commitment) + BOOST_SERIALIZE(blinded_asset_id) BOOST_SERIALIZE(encrypted_amount) BOOST_SERIALIZE(mix_attr) - BOOST_SERIALIZE(etc_details) END_BOOST_SERIALIZATION() }; @@ -444,51 +434,69 @@ namespace currency END_BOOST_SERIALIZATION() }; - // non-consoditated txs must have one of this objects in the attachments (outputs_count == vout.size()) - // consolidated -- one pre consolidated part (sum(outputs_count) == vout.size()) + struct zc_asset_surjection_proof + { + std::vector bge_proofs; // one per output, non-aggregated version of Groth-Bootle-Esgin yet, need to be upgraded later -- sowle + + BEGIN_SERIALIZE_OBJECT() + FIELD(bge_proofs) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(bge_proofs) + END_BOOST_SERIALIZATION() + }; + + // non-consoditated txs must have one of this objects in the attachments (elements count == vout.size()) + // consolidated -- one pre consolidated part (sum(elements count) == vout.size()) struct zc_outs_range_proof { - crypto::bpp_signature_serialized bpp; - uint8_t outputs_count = 0; // how many outputs are included in the proof + crypto::bpp_signature_serialized bpp; // for commitments in form: amount * U + mask * G + crypto::vector_UG_aggregation_proof_serialized aggregation_proof; // E'_j = e_j * U + y'_j * G + vector Shnorr BEGIN_SERIALIZE_OBJECT() FIELD(bpp) - FIELD(outputs_count) + FIELD(aggregation_proof) END_SERIALIZE() BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(bpp) - BOOST_SERIALIZE(outputs_count) + BOOST_SERIALIZE(aggregation_proof) END_BOOST_SERIALIZATION() }; - // Zarcanum-aware CLSAG signature + // Zarcanum-aware CLSAG signature (one per ZC input) struct ZC_sig { crypto::public_key pseudo_out_amount_commitment = null_pkey; // premultiplied by 1/8 - crypto::CLSAG_GG_signature_serialized clsags_gg; + crypto::public_key pseudo_out_blinded_asset_id = null_pkey; // premultiplied by 1/8 + crypto::CLSAG_GGX_signature_serialized clsags_ggx; BEGIN_SERIALIZE_OBJECT() FIELD(pseudo_out_amount_commitment) - FIELD(clsags_gg) + FIELD(pseudo_out_blinded_asset_id) + FIELD(clsags_ggx) END_SERIALIZE() BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(pseudo_out_amount_commitment) - BOOST_SERIALIZE(clsags_gg) + BOOST_SERIALIZE(pseudo_out_blinded_asset_id) + BOOST_SERIALIZE(clsags_ggx) END_BOOST_SERIALIZATION() }; + // 1) for txs without ZC inputs: proves that balance point = lin(G) (cancels out G component of outputs' amount commitments, asset tags assumed to be H (native coin) and non-blinded) + // 2) for txs with ZC inputs: proves that balance point = lin(X) (cancels out X component of blinded asset tags within amount commitments for both outputs and inputs (pseudo outs)) struct zc_balance_proof { - crypto::signature s = null_sig; + crypto::generic_schnorr_sig_s ss; BEGIN_SERIALIZE_OBJECT() - FIELD(s) + FIELD(ss) END_SERIALIZE() BEGIN_BOOST_SERIALIZATION() - BOOST_SERIALIZE(s) + BOOST_SERIALIZE(ss) END_BOOST_SERIALIZATION() }; @@ -506,9 +514,9 @@ namespace currency FIELD(y2); FIELD(y3); FIELD(y4); - FIELD((crypto::bppe_signature_serialized&)E_range_proof); + FIELD_N("E_range_proof", (crypto::bppe_signature_serialized&)E_range_proof); FIELD(pseudo_out_amount_commitment); - FIELD((crypto::CLSAG_GGXG_signature_serialized&)clsag_ggxg); + FIELD_N("clsag_ggxxg", (crypto::CLSAG_GGXXG_signature_serialized&)clsag_ggxxg); END_SERIALIZE() BEGIN_BOOST_SERIALIZATION() @@ -524,7 +532,7 @@ namespace currency BOOST_SERIALIZE(y4); BOOST_SERIALIZE((crypto::bppe_signature_serialized&)E_range_proof); BOOST_SERIALIZE(pseudo_out_amount_commitment); - BOOST_SERIALIZE((crypto::CLSAG_GGXG_signature_serialized&)clsag_ggxg); + BOOST_SERIALIZE((crypto::CLSAG_GGXXG_signature_serialized&)clsag_ggxxg); END_BOOST_SERIALIZATION() }; @@ -740,7 +748,8 @@ namespace currency std::string ticker; std::string full_name; std::string meta_info; - crypto::public_key owner = currency::null_pkey; + crypto::public_key owner = currency::null_pkey; // consider premultipling by 1/8 + bool hidden_supply = false; BEGIN_VERSIONED_SERIALIZE() FIELD(total_max_supply) @@ -750,6 +759,7 @@ namespace currency FIELD(full_name) FIELD(meta_info) FIELD(owner) + FIELD(hidden_supply) END_SERIALIZE() @@ -777,7 +787,7 @@ namespace currency struct asset_descriptor_with_id: public asset_descriptor_base { - crypto::hash asset_id = currency::null_hash; + crypto::public_key asset_id = currency::null_pkey; /* BEGIN_VERSIONED_SERIALIZE() @@ -797,28 +807,42 @@ namespace currency #define ASSET_DESCRIPTOR_OPERATION_REGISTER 1 #define ASSET_DESCRIPTOR_OPERATION_EMMIT 2 #define ASSET_DESCRIPTOR_OPERATION_UPDATE 3 +#define ASSET_DESCRIPTOR_OPERATION_PUBLIC_BURN 4 struct asset_descriptor_operation { uint8_t operation_type = ASSET_DESCRIPTOR_OPERATION_UNDEFINED; - std::vector proof; asset_descriptor_base descriptor; - std::vector asset_id; //questionable regarding form of optional fields - + boost::optional opt_amount_commitment; // premultiplied by 1/8 BEGIN_VERSIONED_SERIALIZE() FIELD(operation_type) - FIELD(proof) FIELD(descriptor) - FIELD(asset_id) + FIELD(opt_amount_commitment) END_SERIALIZE() - BEGIN_BOOST_SERIALIZATION() BOOST_SERIALIZE(operation_type) - BOOST_SERIALIZE(proof) BOOST_SERIALIZE(descriptor) + BOOST_SERIALIZE(opt_amount_commitment) + END_BOOST_SERIALIZATION() + }; + + struct asset_operation_proof + { + // linear composition proof for the fact amount_commitment = lin(asset_id, G) + boost::optional opt_amount_commitment_composition_proof; // for hidden supply + boost::optional opt_amount_commitment_g_proof; // for non-hidden supply, proofs that amount_commitment - supply * asset_id = lin(G) + + BEGIN_VERSIONED_SERIALIZE() + FIELD(opt_amount_commitment_composition_proof) + FIELD(opt_amount_commitment_g_proof) + END_SERIALIZE() + + BEGIN_BOOST_SERIALIZATION() + BOOST_SERIALIZE(opt_amount_commitment_composition_proof) + BOOST_SERIALIZE(opt_amount_commitment_g_proof) END_BOOST_SERIALIZATION() }; @@ -889,10 +913,10 @@ namespace currency END_SERIALIZE() }; - typedef boost::mpl::vector25< + typedef boost::mpl::vector23< tx_service_attachment, tx_comment, tx_payer_old, tx_receiver_old, tx_derivation_hint, std::string, tx_crypto_checksum, etc_tx_time, etc_tx_details_unlock_time, etc_tx_details_expiration_time, etc_tx_details_flags, crypto::public_key, extra_attachment_info, extra_alias_entry_old, extra_user_data, extra_padding, etc_tx_flags16_t, etc_tx_details_unlock_time2, - tx_payer, tx_receiver, extra_alias_entry, zarcanum_tx_data_v1, zc_outs_range_proof, zc_balance_proof, asset_descriptor_operation + tx_payer, tx_receiver, extra_alias_entry, zarcanum_tx_data_v1, asset_descriptor_operation > all_payload_types; typedef boost::make_variant_over::type payload_items_v; @@ -943,7 +967,7 @@ namespace currency typedef boost::variant signature_v; - + typedef boost::variant proof_v; //include backward compatibility defintions @@ -952,11 +976,9 @@ namespace currency class transaction_prefix { public: - // tx version information - uint64_t version{}; - //extra - std::vector extra; + uint64_t version = 0; std::vector vin; + std::vector extra; std::vector vout; BEGIN_SERIALIZE() @@ -965,12 +987,9 @@ namespace currency CHAIN_TRANSITION_VER(TRANSACTION_VERSION_PRE_HF4, transaction_prefix_v1) if(CURRENT_TRANSACTION_VERSION < version) return false; FIELD(vin) - FIELD(vout) FIELD(extra) + FIELD(vout) END_SERIALIZE() - - protected: - transaction_prefix(){} }; /* @@ -986,55 +1005,23 @@ namespace currency class transaction: public transaction_prefix { public: - std::vector signatures; std::vector attachment; - - transaction(); + std::vector signatures; + std::vector proofs; BEGIN_SERIALIZE_OBJECT() FIELDS(*static_cast(this)) CHAIN_TRANSITION_VER(TRANSACTION_VERSION_INITAL, transaction_v1) CHAIN_TRANSITION_VER(TRANSACTION_VERSION_PRE_HF4, transaction_v1) - FIELD(signatures) FIELD(attachment) + FIELD(signatures) + FIELD(proofs) END_SERIALIZE() }; - inline - transaction::transaction() - { - version = 0; - vin.clear(); - vout.clear(); - extra.clear(); - signatures.clear(); - attachment.clear(); - - } - /* - inline - transaction::~transaction() - { - //set_null(); - } - - inline - void transaction::set_null() - { - version = 0; - unlock_time = 0; - vin.clear(); - vout.clear(); - extra.clear(); - signatures.clear(); - } - */ - - - /************************************************************************/ /* */ @@ -1096,8 +1083,6 @@ namespace currency */ //------------------------------------------------------------------------------------------------------------------- - - #pragma pack(push, 1) struct stake_modifier_type { @@ -1107,7 +1092,6 @@ namespace currency struct stake_kernel { - stake_modifier_type stake_modifier; uint64_t block_timestamp; //this block timestamp crypto::key_image kimage; @@ -1140,6 +1124,15 @@ namespace currency END_KV_SERIALIZE_MAP() }; + bool operator ==(const currency::transaction& a, const currency::transaction& b); + bool operator ==(const currency::block& a, const currency::block& b); + bool operator ==(const currency::extra_attachment_info& a, const currency::extra_attachment_info& b); + bool operator ==(const currency::NLSAG_sig& a, const currency::NLSAG_sig& b); + bool operator ==(const currency::void_sig& a, const currency::void_sig& b); + bool operator ==(const currency::ZC_sig& a, const currency::ZC_sig& b); + bool operator ==(const currency::zarcanum_sig& a, const currency::zarcanum_sig& b); + bool operator ==(const currency::ref_by_id& a, const currency::ref_by_id& b); + } // namespace currency POD_MAKE_HASHABLE(currency, account_public_address); @@ -1215,12 +1208,12 @@ SET_VARIANT_TAGS(currency::NLSAG_sig, 42, "NLSAG_sig"); SET_VARIANT_TAGS(currency::ZC_sig, 43, "ZC_sig"); SET_VARIANT_TAGS(currency::void_sig, 44, "void_sig"); SET_VARIANT_TAGS(currency::zarcanum_sig, 45, "zarcanum_sig"); -SET_VARIANT_TAGS(currency::zc_outs_range_proof, 46, "zc_outs_range_proof"); -SET_VARIANT_TAGS(currency::zc_balance_proof, 47, "zc_balance_proof"); +SET_VARIANT_TAGS(currency::zc_asset_surjection_proof, 46, "zc_asset_surjection_proof"); +SET_VARIANT_TAGS(currency::zc_outs_range_proof, 47, "zc_outs_range_proof"); +SET_VARIANT_TAGS(currency::zc_balance_proof, 48, "zc_balance_proof"); -SET_VARIANT_TAGS(currency::open_asset_id, 48, "asset_id"); - -SET_VARIANT_TAGS(currency::asset_descriptor_operation, 48, "asset_descriptor_base"); +SET_VARIANT_TAGS(currency::asset_descriptor_operation, 49, "asset_descriptor_base"); +SET_VARIANT_TAGS(currency::asset_operation_proof, 50, "asset_operation_proof"); diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index fd93da34..a2d564df 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -1,4 +1,4 @@ -// Copyright (c) 2014-2018 Zano Project +// Copyright (c) 2014-2023 Zano Project // Copyright (c) 2014-2018 The Louisdor Project // Copyright (c) 2012-2013 The Cryptonote developers // Copyright (c) 2012-2013 The Boolberry developers @@ -14,6 +14,7 @@ using namespace epee; #include "print_fixed_point_helper.h" #include "currency_format_utils.h" +#include "currency_format_utils_transactions.h" #include "serialization/binary_utils.h" #include "serialization/stl_containers.h" #include "currency_core/currency_config.h" @@ -33,6 +34,8 @@ using namespace epee; #include "crypto/bitcoin/sha256_helper.h" #include "crypto_config.h" +#define DBG_VAL_PRINT(x) ((void)0) // LOG_PRINT_CYAN(std::setw(42) << std::left << #x ":" << x, LOG_LEVEL_0) + namespace currency { @@ -42,57 +45,211 @@ namespace currency tx.extra.push_back(alinfo); return true; } - - //--------------------------------------------------------------- - /* - bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, - size_t current_block_size, - uint64_t fee, - const account_public_address &miner_address, - transaction& tx, - const blobdata& extra_nonce, - size_t max_outs) - { - - alias_info alias = AUTO_VAL_INIT(alias); - return construct_miner_tx(height, median_size, already_generated_coins, current_block_size, - fee, - miner_address, - tx, - extra_nonce, - max_outs, - alias, - false, - pos_entry()); - }*/ - - //-------------------------------------------------------------------------------- - bool generate_zc_outs_range_proof(size_t out_index_start, size_t outs_count, const crypto::scalar_vec_t& amounts, const crypto::scalar_vec_t& blinding_masks, - const std::vector& vouts, zc_outs_range_proof& result) + bool generate_asset_surjection_proof(const crypto::hash& context_hash, bool has_non_zc_inputs, outputs_generation_context& ogc, zc_asset_surjection_proof& result) { - //TODO: review for Andre - CHECK_AND_ASSERT_MES(amounts.size() == outs_count, false, ""); - CHECK_AND_ASSERT_MES(blinding_masks.size() == outs_count, false, ""); - CHECK_AND_ASSERT_MES(out_index_start + outs_count == vouts.size(), false, ""); + bool r = false; + size_t outs_count = ogc.blinded_asset_ids.size(); + CHECK_AND_ASSERT_MES(outs_count > 0, false, "blinded_asset_ids shouldn't be empty"); + CHECK_AND_ASSERT_MES(outs_count == ogc.asset_id_blinding_masks.size(), false, "asset_id_blinding_masks != outs_count"); - std::vector commitments_1div8; - for (size_t out_index = out_index_start, i = 0; i < outs_count; ++out_index, ++i) + size_t zc_ins_count = ogc.pseudo_outs_blinded_asset_ids.size(); + if (zc_ins_count == 0) { - const tx_out_zarcanum& toz = boost::get(vouts[out_index]); // may throw an exception, only zarcanum outputs are exprected - const crypto::public_key* p = &toz.amount_commitment; - commitments_1div8.push_back(p); + // if there's no ZC inputs all the outputs must be native coins with explicit asset ids + for(size_t j = 0; j < ogc.blinded_asset_ids.size(); ++j) + { + CHECK_AND_ASSERT_MES(ogc.blinded_asset_ids[j] == currency::native_coin_asset_id_pt, false, "no ZC ins: out #" << j << " has a non-explicit asset id"); + CHECK_AND_ASSERT_MES(ogc.asset_id_blinding_masks[j] == 0, false, "no ZC ins: out #" << j << " has non-zero asset id blinding mask"); + } + return true; + } + + // okay, have some ZC inputs + + CHECK_AND_ASSERT_MES(zc_ins_count == ogc.pseudo_outs_plus_real_out_blinding_masks.size(), false, "zc_ins_count != pseudo_outs_plus_real_out_blinding_masks"); + CHECK_AND_ASSERT_MES(zc_ins_count == ogc.real_zc_ins_asset_ids.size(), false, "zc_ins_count != real_zc_ins_asset_ids"); + + // ins + //ogc.pseudo_outs_blinded_asset_ids; // T^p_i = T_real + r'_i * X + //ogc.pseudo_outs_plus_real_out_blinding_masks; // r_pi + r'_j + + // outs + //ogc.blinded_asset_ids; // T'_j = H_j + s_j * X + //ogc.asset_id_blinding_masks; // s_j + + for(size_t j = 0; j < outs_count; ++j) + { + const crypto::public_key H = ogc.asset_ids[j].to_public_key(); + const crypto::point_t& T = ogc.blinded_asset_ids[j]; + + std::vector ring; + ring.reserve(zc_ins_count); + size_t secret_index = SIZE_MAX; + crypto::scalar_t secret = -ogc.asset_id_blinding_masks[j]; + + for(size_t i = 0; i < zc_ins_count; ++i) + { + ring.emplace_back(ogc.pseudo_outs_blinded_asset_ids[i] - T); + if (secret_index == SIZE_MAX && ogc.real_zc_ins_asset_ids[i] == H) + { + secret_index = i; + secret += ogc.pseudo_outs_plus_real_out_blinding_masks[secret_index]; + } + } + + // additional ring member for native coins in txs with non-zc inputs + if (has_non_zc_inputs) + { + ring.emplace_back(currency::native_coin_asset_id_pt - T); + if (secret_index == SIZE_MAX && H == native_coin_asset_id) + secret_index = ring.size() - 1; + } + + // additional ring member for asset emitting operation + if (!ogc.ao_amount_blinding_mask.is_zero()) + { + ring.emplace_back(ogc.ao_asset_id_pt - T); + if (secret_index == SIZE_MAX && H == ogc.ao_asset_id) + secret_index = ring.size() - 1; + } + + CHECK_AND_ASSERT_MES(secret_index != SIZE_MAX, false, "out #" << j << ": can't find a corresponding asset id in inputs"); + + result.bge_proofs.emplace_back(crypto::BGE_proof_s{}); + uint8_t err = 0; + r = crypto::generate_BGE_proof(context_hash, ring, secret, secret_index, result.bge_proofs.back(), &err); + CHECK_AND_ASSERT_MES(r, false, "out #" << j << ": generate_BGE_proof failed with err=" << (int)err); } - result.outputs_count = outs_count; + return true; + } + //-------------------------------------------------------------------------------- + bool verify_asset_surjection_proof(const transaction& tx, const crypto::hash& tx_id) + { + bool r = false; + if (tx.version <= TRANSACTION_VERSION_PRE_HF4) + return true; + + size_t outs_count = tx.vout.size(); + + bool has_ZC_inputs = false; + bool has_non_ZC_inputs = false; + for(const auto& in : tx.vin) + { + if (in.type() == typeid(txin_zc_input)) + has_ZC_inputs = true; + else + has_non_ZC_inputs = true; + } + + if (!has_ZC_inputs) + { + // no ZC ins -- just make sure there's only native outputs with explicit asset ids + for(size_t j = 0; j < outs_count; ++j) + { + CHECK_AND_ASSERT_MES(boost::get(tx.vout[j]).blinded_asset_id == native_coin_asset_id_1div8, false, "output #" << j << " has a non explicitly native asset id"); + } + return true; + } + + // tx has ZC inputs + const zc_asset_surjection_proof& sig = get_type_in_variant_container_by_ref(tx.proofs); // order of proofs and uniqueness of zc_asset_surjection_proof should be check before on prevalidation + CHECK_AND_ASSERT_MES(sig.bge_proofs.size() == outs_count, false, "ASP count: " << sig.bge_proofs.size() << ", outputs: " << outs_count << " => missmatch"); + + // make a ring + std::vector pseudo_outs_blinded_asset_ids; + for(const auto& sig : tx.signatures) + { + if (sig.type() == typeid(ZC_sig)) + pseudo_outs_blinded_asset_ids.emplace_back(crypto::point_t(boost::get(sig).pseudo_out_blinded_asset_id).modify_mul8()); + } + if (has_non_ZC_inputs) + pseudo_outs_blinded_asset_ids.emplace_back(currency::native_coin_asset_id_pt); // additional ring member for txs with non-zc inputs + + asset_descriptor_operation ado{}; + if (is_asset_emitting_transaction(tx, &ado)) + { + crypto::point_t asset_id_pt = crypto::c_point_0; + calculate_asset_id(ado.descriptor.owner, &asset_id_pt, nullptr); // TODO @#@# optimization: this expensive calculation should be done only once + pseudo_outs_blinded_asset_ids.emplace_back(asset_id_pt); // additional ring member for asset emitting tx + } + + for(size_t j = 0; j < outs_count; ++j) + { + crypto::point_t blinded_asset_id(boost::get(tx.vout[j]).blinded_asset_id); + blinded_asset_id.modify_mul8(); + + // TODO @#@# remove this redundant conversion to pubkey and back + std::vector ring(pseudo_outs_blinded_asset_ids.size()); + std::vector ring_pointers(pseudo_outs_blinded_asset_ids.size()); + for(size_t i = 0, n = pseudo_outs_blinded_asset_ids.size(); i < n; ++i) + { + ring[i] = ((crypto::c_scalar_1div8 * (pseudo_outs_blinded_asset_ids[i] - blinded_asset_id)).to_public_key()); + ring_pointers[i] = &ring[i]; + } + + uint8_t err = 0; + CHECK_AND_ASSERT_MES(crypto::verify_BGE_proof(tx_id, ring_pointers, sig.bge_proofs[j], &err), false, "verify_BGE_proof failed, err = " << (int)err); + } + + return true; + } + //-------------------------------------------------------------------------------- + bool generate_zc_outs_range_proof(const crypto::hash& context_hash, size_t out_index_start, const outputs_generation_context& outs_gen_context, + const std::vector& vouts, zc_outs_range_proof& result) + { + size_t outs_count = outs_gen_context.amounts.size(); + // TODO @#@# reconsider this check CHECK_AND_ASSERT_MES(outs_gen_context.check_sizes(outs_count), false, ""); + CHECK_AND_ASSERT_MES(out_index_start + outs_count == vouts.size(), false, ""); + + // prepare data for aggregation proof + std::vector amount_commitments_for_rp_aggregation; // E' = amount * U + y' * G + crypto::scalar_vec_t y_primes; // y' + for (size_t out_index = out_index_start, i = 0; i < outs_count; ++out_index, ++i) + { + crypto::scalar_t y_prime = crypto::scalar_t::random(); + amount_commitments_for_rp_aggregation.emplace_back(outs_gen_context.amounts[i] * crypto::c_point_U + y_prime * crypto::c_point_G); // E'_j = e_j * U + y'_j * G + y_primes.emplace_back(std::move(y_prime)); + } + + // aggregation proof uint8_t err = 0; - bool r = crypto::bpp_gen<>(amounts, blinding_masks, commitments_1div8, result.bpp, &err); + bool r = crypto::generate_vector_UG_aggregation_proof(context_hash, outs_gen_context.amounts, outs_gen_context.amount_blinding_masks, y_primes, + outs_gen_context.amount_commitments, + amount_commitments_for_rp_aggregation, + outs_gen_context.blinded_asset_ids, result.aggregation_proof, &err); + CHECK_AND_ASSERT_MES(r, false, "generate_vector_UG_aggregation_proof failed with error " << (int)err); + + // aggregated range proof + std::vector commitments_1div8(result.aggregation_proof.amount_commitments_for_rp_aggregation.size()); + for(size_t i = 0, sz = result.aggregation_proof.amount_commitments_for_rp_aggregation.size(); i < sz; ++i) + commitments_1div8[i] = &result.aggregation_proof.amount_commitments_for_rp_aggregation[i]; + + err = 0; + r = crypto::bpp_gen(outs_gen_context.amounts, y_primes, commitments_1div8, result.bpp, &err); CHECK_AND_ASSERT_MES(r, false, "bpp_gen failed with error " << (int)err); return true; } - //--------------------------------------------------------------- + bool verify_multiple_zc_outs_range_proofs(const std::vector& range_proofs) + { + if (range_proofs.empty()) + return true; + + std::vector sigs; + sigs.reserve(range_proofs.size()); + for(auto& el : range_proofs) + sigs.emplace_back(el.range_proof.bpp, el.amount_commitments); + + uint8_t err = 0; + bool r = crypto::bpp_verify(sigs, &err); + CHECK_AND_ASSERT_MES(r, false, "bpp_verify failed with error " << (int)err); + + return true; + } + //-------------------------------------------------------------------------------- wide_difficulty_type correct_difficulty_with_sequence_factor(size_t sequence_factor, wide_difficulty_type diff) { //delta=delta*(0.75^n) @@ -103,54 +260,72 @@ namespace currency return diff; } //------------------------------------------------------------------ - // for txs with no zc inputs (and thus no zc signatures) but with zc outputs - bool generate_tx_balance_proof(transaction &tx, const crypto::scalar_t& outputs_blinding_masks_sum, uint64_t block_reward_for_miner_tx = 0) + bool generate_tx_balance_proof(const transaction &tx, const crypto::hash& tx_id, const outputs_generation_context& ogc, uint64_t block_reward_for_miner_tx, currency::zc_balance_proof& proof) { CHECK_AND_ASSERT_MES(tx.version > TRANSACTION_VERSION_PRE_HF4, false, "unsupported tx.version: " << tx.version); - CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.signatures) == 0, false, ""); + CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.proofs) == 0, false, "zc_balance_proof is already present"); + bool r = false; uint64_t bare_inputs_sum = block_reward_for_miner_tx; - // TODO: condider remove the followin cycle + size_t zc_inputs_count = 0; for(auto& vin : tx.vin) { VARIANT_SWITCH_BEGIN(vin); - VARIANT_CASE(txin_to_key, tk) + VARIANT_CASE_CONST(txin_to_key, tk) bare_inputs_sum += tk.amount; - VARIANT_CASE(txin_htlc, foo); + VARIANT_CASE_CONST(txin_htlc, foo); CHECK_AND_ASSERT_MES(false, false, "unexpected txin_htlc input"); - VARIANT_CASE(txin_multisig, ms); - bare_inputs_sum += ms.amount; - VARIANT_CASE(txin_zc_input, foo); - CHECK_AND_ASSERT_MES(false, false, "unexpected txin_zc_input input"); + VARIANT_CASE_CONST(txin_multisig, ms); + //bare_inputs_sum += ms.amount; + CHECK_AND_ASSERT_MES(false, false, "unexpected txin_multisig input"); // TODO @#@# check support for multisig inputs + VARIANT_CASE_CONST(txin_zc_input, foo); + ++zc_inputs_count; VARIANT_SWITCH_END(); } - crypto::point_t outs_commitments_sum = crypto::c_point_0; - for(auto& vout : tx.vout) - { - CHECK_AND_ASSERT_MES(vout.type() == typeid(tx_out_zarcanum), false, "unexpected type in outs: " << vout.type().name()); - const tx_out_zarcanum& ozc = boost::get(vout); - outs_commitments_sum += crypto::point_t(ozc.amount_commitment); // amount_commitment premultiplied by 1/8 - } - outs_commitments_sum.modify_mul8(); - uint64_t fee = 0; CHECK_AND_ASSERT_MES(get_tx_fee(tx, fee), false, "unable to get tx fee"); - // sum(bare inputs' amounts) * H + sum(pseudo outs commitments for ZC inputs) + residual * G = sum(outputs' commitments) + fee * H - // <=> - // (fee - sum(bare inputs' amounts)) * H - sum(pseudo outs commitments for ZC inputs) + sum(outputs' commitments) = residual * G + if (zc_inputs_count == 0) + { + // no ZC inputs => all inputs are bare inputs; all outputs have explicit asset_id = native_coin_asset_id; in main balance equation we only need to cancel out G-component + CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.signatures) == 0, false, "ZC_sig is unexpected"); + CHECK_AND_ASSERT_MES(ogc.asset_id_blinding_mask_x_amount_sum.is_zero(), false, "it's expected that all asset ids for this tx are obvious and thus explicit"); // because this tx has no ZC inputs => all outs clearly have native asset id + CHECK_AND_ASSERT_MES(ogc.ao_amount_blinding_mask.is_zero(), false, "asset emmission is not allowed for txs without ZC inputs"); - // tx doesn't have any zc inputs --> add Schnorr proof for commitment to zero - CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.attachment) == 0, false, ""); - zc_balance_proof balance_proof = AUTO_VAL_INIT(balance_proof); + // (sum(bare inputs' amounts) - fee) * H + sum(pseudo out amount commitments) - sum(outputs' commitments) = lin(G) - crypto::point_t commitment_to_zero = outs_commitments_sum + (crypto::scalar_t(fee) - crypto::scalar_t(bare_inputs_sum)) * crypto::c_point_H; - //crypto::scalar_t witness = outputs_blinding_masks_sum; + crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt - ogc.amount_commitments_sum; + crypto::scalar_t secret_x = -ogc.amount_blinding_masks_sum; +#ifndef NDEBUG + CHECK_AND_ASSERT_MES(commitment_to_zero == secret_x * crypto::c_point_G, false, "internal error: commitment_to_zero is malformed (G)"); +#endif + r = crypto::generate_schnorr_sig(tx_id, commitment_to_zero, secret_x, proof.ss); + CHECK_AND_ASSERT_MES(r, false, "generate_schnorr_sig (G) failed"); + } + else // i.e. zc_inputs_count != 0 + { + // there're ZC inputs => in main balance equation we only need to cancel out X-component, because G-component cancelled out by choosing blinding mask for the last pseudo out amount commitment - // TODO: consider adding more data to message - crypto::generate_signature(null_hash, commitment_to_zero.to_public_key(), outputs_blinding_masks_sum.as_secret_key(), balance_proof.s); - tx.attachment.push_back(balance_proof); + crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt + ogc.pseudo_out_amount_commitments_sum + ogc.ao_amount_commitment - ogc.amount_commitments_sum; + crypto::scalar_t secret_x = ogc.real_in_asset_id_blinding_mask_x_amount_sum - ogc.asset_id_blinding_mask_x_amount_sum; + + DBG_VAL_PRINT(bare_inputs_sum); + DBG_VAL_PRINT(fee); + DBG_VAL_PRINT(ogc.pseudo_out_amount_commitments_sum); + DBG_VAL_PRINT(ogc.ao_amount_commitment); + DBG_VAL_PRINT(ogc.amount_commitments_sum); + DBG_VAL_PRINT(ogc.real_in_asset_id_blinding_mask_x_amount_sum); + DBG_VAL_PRINT(ogc.asset_id_blinding_mask_x_amount_sum); + DBG_VAL_PRINT(commitment_to_zero); + DBG_VAL_PRINT(secret_x); + +#ifndef NDEBUG + CHECK_AND_ASSERT_MES(commitment_to_zero == secret_x * crypto::c_point_X, false, "internal error: commitment_to_zero is malformed (X)"); +#endif + r = crypto::generate_schnorr_sig(tx_id, commitment_to_zero, secret_x, proof.ss); + CHECK_AND_ASSERT_MES(r, false, "generate_schnorr_sig (X) failed"); + } return true; } @@ -187,7 +362,7 @@ namespace currency size_t max_outs /* = CURRENCY_MINER_TX_MAX_OUTS */, bool pos /* = false */, const pos_entry& pe /* = pos_entry() */, // only pe.stake_unlock_time and pe.stake_amount are used now, TODO: consider refactoring -- sowle - crypto::scalar_t* blinding_masks_sum_ptr /* = nullptr */, + outputs_generation_context* ogc_ptr /* = nullptr */, const keypair* tx_one_time_key_to_use /* = nullptr */ ) { @@ -231,6 +406,7 @@ namespace currency tx_destination_entry de = AUTO_VAL_INIT(de); de.addr.push_back(miner_address); de.amount = a; + de.explicit_native_asset_id = true; // don't use asset id blinding as it's obvious which asset it is if (pe.stake_unlock_time && pe.stake_unlock_time > height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW) { //this means that block is creating after hardfork_1 and unlock_time is needed to set for every destination separately @@ -245,6 +421,7 @@ namespace currency if (pe.stake_unlock_time && pe.stake_unlock_time > height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW) stake_lock_time = pe.stake_unlock_time; destinations.push_back(tx_destination_entry(pe.amount, stakeholder_address, stake_lock_time)); + destinations.back().explicit_native_asset_id = true; // don't use asset id blinding as it's obvious which asset it is } CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS || height == 0, false, "Too many outs (" << destinations.size() << ")! Miner tx can't be constructed."); @@ -265,6 +442,8 @@ namespace currency //we always add extra_padding with 2 bytes length to make possible for get_block_template to adjust cumulative size tx.extra.push_back(extra_padding()); + size_t zc_ins_count = 0; + // input #0: txin_gen txin_gen in; in.height = height; @@ -278,6 +457,7 @@ namespace currency // just placeholders, they will be filled in wallet2::prepare_and_sign_pos_block() tx.vin.emplace_back(std::move(txin_zc_input())); tx.signatures.emplace_back(std::move(zarcanum_sig())); + ++zc_ins_count; } else { @@ -289,37 +469,25 @@ namespace currency } // fill outputs - crypto::scalar_vec_t blinding_masks(destinations.size()); // vector of secret blinging masks for each output. For range proof generation - crypto::scalar_vec_t amounts(destinations.size()); // vector of amounts, converted to scalars. For ranage proof generation - crypto::scalar_t blinding_masks_sum = 0; + outputs_generation_context outs_gen_context(zc_ins_count, destinations.size()); // auxiliary data for each output uint64_t output_index = 0; for (auto& d : destinations) { std::set deriv_cache; finalized_tx result = AUTO_VAL_INIT(result); uint8_t tx_outs_attr = 0; - r = construct_tx_out(d, txkey.sec, output_index, tx, deriv_cache, account_keys(), blinding_masks[output_index], result, tx_outs_attr); + r = construct_tx_out(d, txkey.sec, output_index, tx, deriv_cache, account_keys(), + outs_gen_context.asset_id_blinding_masks[output_index], outs_gen_context.amount_blinding_masks[output_index], + outs_gen_context.blinded_asset_ids[output_index], outs_gen_context.amount_commitments[output_index], result, tx_outs_attr); CHECK_AND_ASSERT_MES(r, false, "construct_tx_out failed, output #" << output_index << ", amount: " << print_money_brief(d.amount)); - amounts[output_index] = d.amount; - blinding_masks_sum += blinding_masks[output_index]; + outs_gen_context.amounts[output_index] = d.amount; + outs_gen_context.asset_ids[output_index] = crypto::point_t(d.asset_id); + outs_gen_context.asset_id_blinding_mask_x_amount_sum += outs_gen_context.asset_id_blinding_masks[output_index] * d.amount; + outs_gen_context.amount_blinding_masks_sum += outs_gen_context.amount_blinding_masks[output_index]; + outs_gen_context.amount_commitments_sum += outs_gen_context.amount_commitments[output_index]; ++output_index; } - if (tx.version > TRANSACTION_VERSION_PRE_HF4) - { - //add range proofs - currency::zc_outs_range_proof range_proofs = AUTO_VAL_INIT(range_proofs); - bool r = generate_zc_outs_range_proof(0, amounts.size(), amounts, blinding_masks, tx.vout, range_proofs); - CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); - tx.attachment.push_back(range_proofs); - - if (!pos) - { - r = generate_tx_balance_proof(tx, blinding_masks_sum, block_reward); - CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); - } - } - if (tx.attachment.size()) add_attachments_info_to_extra(tx.extra, tx.attachment); @@ -329,26 +497,103 @@ namespace currency set_tx_unlock_time(tx, height + CURRENCY_MINED_MONEY_UNLOCK_WINDOW); } - if (blinding_masks_sum_ptr) - *blinding_masks_sum_ptr = blinding_masks_sum; + if (ogc_ptr) + *ogc_ptr = outs_gen_context; // TODO @#@# consider refactoring (a lot of copying) -- sowle + + + if (tx.version > TRANSACTION_VERSION_PRE_HF4 && !pos) + { + // This is for PoW blocks only, because PoS blocks proofs are handled in wallet2::prepare_and_sign_pos_block() due to the necessity of making Zarcanum proofs first + // + // tx hash should be sealed by now + crypto::hash tx_id = get_transaction_hash(tx); + + // asset surjection proof + currency::zc_asset_surjection_proof asp{}; + bool r = generate_asset_surjection_proof(tx_id, true, outs_gen_context, asp); + CHECK_AND_ASSERT_MES(r, false, "generete_asset_surjection_proof failed"); + tx.proofs.emplace_back(std::move(asp)); + + // range proofs + currency::zc_outs_range_proof range_proofs{}; + r = generate_zc_outs_range_proof(tx_id, 0, outs_gen_context, tx.vout, range_proofs); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); + tx.proofs.emplace_back(std::move(range_proofs)); + + // balance proof + currency::zc_balance_proof balance_proof{}; + r = generate_tx_balance_proof(tx, tx_id, outs_gen_context, block_reward, balance_proof); + CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); + tx.proofs.emplace_back(std::move(balance_proof)); + } + + return true; } //------------------------------------------------------------------ - bool check_tx_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx /* = 0 */) + bool check_tx_bare_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx /* = 0 */) + { + // legacy checks for old fashioned tx with non-hidden amounts + CHECK_AND_ASSERT_MES(tx.version <= TRANSACTION_VERSION_PRE_HF4, false, "check_tx_bare_balance can't check post-HF4 txs"); + uint64_t bare_outputs_sum = get_outs_money_amount(tx); + uint64_t bare_inputs_sum = get_inputs_money_amount(tx); + + if (additional_inputs_amount_and_fees_for_mining_tx == 0) + { + // normal tx + CHECK_AND_ASSERT_MES(bare_inputs_sum >= bare_outputs_sum, false, "tx balance error: sum of inputs (" << print_money_brief(bare_inputs_sum) + << ") is less than or equal to sum of outputs(" << print_money_brief(bare_outputs_sum) << ")"); + } + else + { + // miner tx + CHECK_AND_ASSERT_MES(bare_inputs_sum + additional_inputs_amount_and_fees_for_mining_tx == bare_outputs_sum, false, + "tx balance error: sum of inputs (" << print_money_brief(bare_inputs_sum) << + ") + additional inputs and fees (" << print_money_brief(additional_inputs_amount_and_fees_for_mining_tx) << + ") is less than or equal to sum of outputs(" << print_money_brief(bare_outputs_sum) << ")"); + } + return true; + } + //----------------------------------------------------------------------------------------------- + bool validate_asset_operation(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, crypto::public_key& asset_id) + { + crypto::point_t asset_id_pt = crypto::c_point_0; + calculate_asset_id(ado.descriptor.owner, &asset_id_pt, &asset_id); + + CHECK_AND_ASSERT_MES(count_type_in_variant_container(tx.proofs) == 1, false, "asset_operation_proof not present or present more than once"); + const asset_operation_proof& aop = get_type_in_variant_container_by_ref(tx.proofs); + + if (ado.descriptor.hidden_supply) + { + CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_composition_proof.has_value(), false, "opt_amount_commitment_composition_proof is absent"); + // TODO @#@# if asset is hidden -- theck composition proof + return false; + } + else + { + // make sure that amount commitment corresponds to opt_amount_commitment_g_proof + CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is absent"); + CHECK_AND_ASSERT_MES(aop.opt_amount_commitment_g_proof.has_value(), false, "opt_amount_commitment_g_proof is absent"); + crypto::point_t A = crypto::point_t(ado.opt_amount_commitment.get()).modify_mul8() - ado.descriptor.current_supply * asset_id_pt; + + bool r = crypto::check_signature(tx_id, A.to_public_key(), aop.opt_amount_commitment_g_proof.get()); + CHECK_AND_ASSERT_MES(r, false, "opt_amount_commitment_g_proof check failed"); + } + + return true; + } + //------------------------------------------------------------------ + bool check_tx_balance(const transaction& tx, const crypto::hash& tx_id, uint64_t additional_inputs_amount_and_fees_for_mining_tx /* = 0 */) { if (tx.version > TRANSACTION_VERSION_PRE_HF4) { - //@#@ TODO: This is just a temporary code - uint64_t assets_emmited = 0; - asset_descriptor_operation ado = AUTO_VAL_INIT(ado); - if (get_type_in_variant_container(tx.extra, ado) && ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) - { - assets_emmited += ado.descriptor.current_supply; - } + zc_balance_proof balance_proof = AUTO_VAL_INIT(balance_proof); + bool r = get_type_in_variant_container(tx.proofs, balance_proof); + CHECK_AND_ASSERT_MES(r, false, "zc_balance_proof is missing in tx proofs"); size_t zc_inputs_count = 0; - uint64_t bare_inputs_sum = additional_inputs_amount_and_fees_for_mining_tx + assets_emmited; + uint64_t bare_inputs_sum = additional_inputs_amount_and_fees_for_mining_tx; for(auto& vin : tx.vin) { VARIANT_SWITCH_BEGIN(vin); @@ -363,7 +608,7 @@ namespace currency VARIANT_SWITCH_END(); } - crypto::point_t outs_commitments_sum = crypto::c_point_0; + crypto::point_t outs_commitments_sum = crypto::c_point_0; // TODO: consider adding additional commitments / spends / burns here for(auto& vout : tx.vout) { CHECK_AND_ASSERT_MES(vout.type() == typeid(tx_out_zarcanum), false, "unexpected type in outs: " << vout.type().name()); @@ -378,8 +623,16 @@ namespace currency CHECK_AND_ASSERT_MES(additional_inputs_amount_and_fees_for_mining_tx == 0 || fee == 0, false, "invalid tx: fee = " << print_money_brief(fee) << ", additional inputs + fees = " << print_money_brief(additional_inputs_amount_and_fees_for_mining_tx)); - size_t zc_sigs_count = 0; crypto::point_t sum_of_pseudo_out_amount_commitments = crypto::c_point_0; + // take into account newly emitted assets + asset_descriptor_operation ado = AUTO_VAL_INIT(ado); + if (get_type_in_variant_container(tx.extra, ado) && ado.operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER) // @#@ TODO: Support other asset operations + { + // opt_amount_commitment supposed to be validated earlier in validate_asset_operation() + CHECK_AND_ASSERT_MES(ado.opt_amount_commitment.has_value(), false, "opt_amount_commitment is not set"); + sum_of_pseudo_out_amount_commitments += crypto::point_t(ado.opt_amount_commitment.get()); // *1/8 + } + size_t zc_sigs_count = 0; for(auto& sig_v : tx.signatures) { VARIANT_SWITCH_BEGIN(sig_v); @@ -393,53 +646,25 @@ namespace currency } sum_of_pseudo_out_amount_commitments.modify_mul8(); - CHECK_AND_ASSERT_MES(zc_inputs_count == zc_sigs_count, false, "zc inputs count (" << zc_inputs_count << ") and zc sigs count (" << zc_sigs_count << ") missmatch"); + // (sum(bare inputs' amounts) - fee) * H + sum(pseudo outs commitments for ZC inputs) - sum(outputs' commitments) = lin(X) OR = lin(G) + crypto::point_t commitment_to_zero = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * currency::native_coin_asset_id_pt + sum_of_pseudo_out_amount_commitments - outs_commitments_sum; + CHECK_AND_ASSERT_MES(zc_inputs_count == zc_sigs_count, false, "zc inputs count (" << zc_inputs_count << ") and zc sigs count (" << zc_sigs_count << ") missmatch"); if (zc_inputs_count > 0) { - // no need for additional Schnorr proof for commitment to zero - - // sum(bare inputs' amounts) * H + sum(pseudo outs commitments for ZC inputs) = sum(outputs' commitments) + fee * H - // <=> - // (sum(bare inputs' amounts) - fee) * H + sum(pseudo outs commitments for ZC inputs) - sum(outputs' commitments) = 0 - crypto::point_t Z = (crypto::scalar_t(bare_inputs_sum) - crypto::scalar_t(fee)) * crypto::c_point_H + sum_of_pseudo_out_amount_commitments - outs_commitments_sum; - CHECK_AND_ASSERT_MES(Z.is_zero(), false, "balace equation does not hold"); + r = crypto::verify_schnorr_sig(tx_id, commitment_to_zero.to_public_key(), balance_proof.ss); + CHECK_AND_ASSERT_MES(r, false, "zc_balance_proof (X) is invalid"); } else { - // no zc inputs -- there should be Schnorr proof for commitment to zero - zc_balance_proof balance_proof = AUTO_VAL_INIT(balance_proof); - bool r = get_type_in_variant_container(tx.attachment, balance_proof); - CHECK_AND_ASSERT_MES(r, false, "no zc inputs are present, but at the same time there's no zc_balance_proof in attachment"); - - // (fee - sum(bare inputs' amounts)) * H + sum(outputs' commitments) = residual * G - crypto::point_t commitment_to_zero = (crypto::scalar_t(fee) - crypto::scalar_t(bare_inputs_sum)) * crypto::c_point_H + outs_commitments_sum; - r = crypto::check_signature(null_hash, commitment_to_zero.to_public_key(), balance_proof.s); - CHECK_AND_ASSERT_MES(r, false, "zc_balance_proof is invalid"); + r = crypto::verify_schnorr_sig(tx_id, commitment_to_zero.to_public_key(), balance_proof.ss); + CHECK_AND_ASSERT_MES(r, false, "zc_balance_proof (G) is invalid"); } + return true; } - else - { - // old fashioned tx with non-hidden amounts - uint64_t bare_outputs_sum = get_outs_money_amount(tx); - uint64_t bare_inputs_sum = get_inputs_money_amount(tx); - - if (additional_inputs_amount_and_fees_for_mining_tx == 0) - { - // normal tx - CHECK_AND_ASSERT_MES(bare_inputs_sum >= bare_outputs_sum, false, "tx balance error: sum of inputs (" << print_money_brief(bare_inputs_sum) - << ") is less than or equal to sum of outputs(" << print_money_brief(bare_outputs_sum) << ")"); - } - else - { - // miner tx - CHECK_AND_ASSERT_MES(bare_inputs_sum + additional_inputs_amount_and_fees_for_mining_tx == bare_outputs_sum, false, - "tx balance error: sum of inputs (" << print_money_brief(bare_inputs_sum) << - ") + additional inputs and fees (" << print_money_brief(additional_inputs_amount_and_fees_for_mining_tx) << - ") is less than or equal to sum of outputs(" << print_money_brief(bare_outputs_sum) << ")"); - } - } - return true; + + // pre-HF4 txs + return check_tx_bare_balance(tx, additional_inputs_amount_and_fees_for_mining_tx); } //------------------------------------------------------------------ bool derive_ephemeral_key_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral) @@ -727,7 +952,7 @@ namespace currency template bool operator()(const t_extra_typename& k) const { - //do notheing for rest + //do nothing for rest return true; } }; @@ -809,6 +1034,8 @@ namespace currency return derive_public_key_from_target_address(destination_addr, tx_sec_key, index, out_eph_public_key, derivation); } //--------------------------------------------------------------- + // derived_sec_key = Hs(domain, 8 * src_sec_key * src_pub_key, index) + // derived_pub_key = derived_sec_key * G bool derive_key_pair_from_key_pair(const crypto::public_key& src_pub_key, const crypto::secret_key& src_sec_key, crypto::secret_key& derived_sec_key, crypto::public_key& derived_pub_key, const char(&hs_domain)[32], uint64_t index) { crypto::key_derivation derivation = AUTO_VAL_INIT(derivation); @@ -878,30 +1105,25 @@ namespace currency return origin_blob; } //--------------------------------------------------------------- - bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, const account_keys& self, uint8_t tx_outs_attr) + bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, const account_keys& self, uint8_t tx_outs_attr /* = CURRENCY_TO_KEY_OUT_RELAXED */) { finalized_tx result = AUTO_VAL_INIT(result); - crypto::scalar_t out_blinding_mask = AUTO_VAL_INIT(out_blinding_mask); - return construct_tx_out(de, tx_sec_key, output_index, tx, deriv_cache, self, out_blinding_mask, result, tx_outs_attr); + crypto::scalar_t asset_blinding_mask{}, amount_blinding_mask{}; + crypto::point_t blinded_asset_id{}, amount_commitment{}; + return construct_tx_out(de, tx_sec_key, output_index, tx, deriv_cache, self, asset_blinding_mask, amount_blinding_mask, blinded_asset_id, amount_commitment, result, tx_outs_attr); } //--------------------------------------------------------------- bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, - const account_keys& self, crypto::scalar_t& out_blinding_mask, finalized_tx& result, uint8_t tx_outs_attr) + const account_keys& self, crypto::scalar_t& asset_blinding_mask, crypto::scalar_t& amount_blinding_mask, crypto::point_t& blinded_asset_id, crypto::point_t& amount_commitment, + finalized_tx& result, uint8_t tx_outs_attr) { if (tx.version > TRANSACTION_VERSION_PRE_HF4) { // create tx_out_zarcanum - CHECK_AND_ASSERT_MES(de.addr.size() == 1, false, "zarcanum multisig not implemented yet"); + CHECK_AND_ASSERT_MES(de.addr.size() == 1, false, "zarcanum multisig not implemented for tx_out_zarcanum yet"); // TODO @#@# implement multisig support tx_out_zarcanum out = AUTO_VAL_INIT(out); - //@#@ - //TODO: TEMPORARY - if (de.asset_id != currency::null_hash) - { - out.etc_details.push_back(open_asset_id{ de.asset_id }); - } - //@#@ const account_public_address& apa = de.addr.front(); if (apa.spend_public_key == null_pkey && apa.view_public_key == null_pkey) @@ -916,16 +1138,22 @@ namespace currency crypto::scalar_t amount_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_MASK, h); out.encrypted_amount = de.amount ^ amount_mask.m_u64[0]; - out_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) - out.amount_commitment = (crypto::c_scalar_1div8 * de.amount * crypto::c_point_H + crypto::c_scalar_1div8 * out_blinding_mask * crypto::c_point_G).to_public_key(); // A = 1/8 * a * H + 1/8 * f * G + asset_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_ASSET_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + blinded_asset_id = crypto::point_t(de.asset_id) + asset_blinding_mask * crypto::c_point_X; + out.blinded_asset_id = (crypto::c_scalar_1div8 * blinded_asset_id).to_public_key(); // T = 1/8 * (H_asset + s * X) + CHECK_AND_ASSERT_MES(!de.explicit_native_asset_id || de.asset_id == currency::native_coin_asset_id, false, "explicit_native_asset_id may be used only with native asset id"); + asset_blinding_mask = de.explicit_native_asset_id ? 0 : crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_ASSET_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + amount_commitment = de.amount * blinded_asset_id + amount_blinding_mask * crypto::c_point_G; + out.amount_commitment = (crypto::c_scalar_1div8 * amount_commitment).to_public_key(); // E = 1/8 * e * T + 1/8 * y * G + out.mix_attr = tx_outs_attr; // TODO @#@# @CZ check this } else { // normal output crypto::public_key derivation = (crypto::scalar_t(tx_sec_key) * crypto::point_t(apa.view_public_key)).modify_mul8().to_public_key(); // d = 8 * r * V - crypto::scalar_t h; // = crypto::hash_helper_t::hs(derivation, output_index); + crypto::scalar_t h = 0; crypto::derivation_to_scalar((const crypto::key_derivation&)derivation, output_index, h.as_secret_key()); // h = Hs(8 * r * V, i) out.stealth_address = (h * crypto::c_point_G + crypto::point_t(apa.spend_public_key)).to_public_key(); @@ -934,8 +1162,19 @@ namespace currency crypto::scalar_t amount_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_MASK, h); out.encrypted_amount = de.amount ^ amount_mask.m_u64[0]; - out_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_BLINDING_MASK, h); // f = Hs(domain_sep, Hs(8 * r * V, i) ) - out.amount_commitment = (crypto::c_scalar_1div8 * de.amount * crypto::c_point_H + crypto::c_scalar_1div8 * out_blinding_mask * crypto::c_point_G).to_public_key(); // A = 1/8 * a * H + 1/8 * f * G + CHECK_AND_ASSERT_MES(!de.explicit_native_asset_id || de.asset_id == currency::native_coin_asset_id, false, "explicit_native_asset_id may be used only with native asset id"); + asset_blinding_mask = de.explicit_native_asset_id ? 0 : crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_ASSET_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + blinded_asset_id = crypto::point_t(de.asset_id) + asset_blinding_mask * crypto::c_point_X; + out.blinded_asset_id = (crypto::c_scalar_1div8 * blinded_asset_id).to_public_key(); // T = 1/8 * (H_asset + s * X) + + amount_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + amount_commitment = de.amount * blinded_asset_id + amount_blinding_mask * crypto::c_point_G; + out.amount_commitment = (crypto::c_scalar_1div8 * amount_commitment).to_public_key(); // E = 1/8 * e * T + 1/8 * y * G + + //LOG_PRINT_CYAN("OUT " << std::setw(2) << output_index << ": " << de.asset_id << " + " << asset_blinding_mask << " x X", LOG_LEVEL_0); + //LOG_PRINT_CYAN(" == " << blinded_asset_id, LOG_LEVEL_0); + //LOG_PRINT_CYAN("OUT " << std::setw(2) << output_index << ": " << crypto::scalar_t(de.amount) << " x " << blinded_asset_id << " + " << amount_blinding_mask << " x G", LOG_LEVEL_0); + //LOG_PRINT_CYAN(" == " << amount_commitment << ", x 1/8 == " << out.amount_commitment, LOG_LEVEL_0); if (de.addr.front().is_auditable()) out.mix_attr = CURRENCY_TO_KEY_OUT_FORCED_NO_MIX; // override mix_attr to 1 for auditable target addresses @@ -956,6 +1195,7 @@ namespace currency { // create tx_out_bare, this section can be removed after HF4 CHECK_AND_ASSERT_MES(de.addr.size() == 1 || (de.addr.size() > 1 && de.minimum_sigs <= de.addr.size()), false, "Invalid destination entry: amount: " << de.amount << " minimum_sigs: " << de.minimum_sigs << " addr.size(): " << de.addr.size()); + CHECK_AND_ASSERT_MES(de.asset_id == currency::native_coin_asset_id, false, "assets are not allowed prior to HF4"); std::vector target_keys; target_keys.reserve(de.addr.size()); @@ -1602,10 +1842,10 @@ namespace currency }; //-------------------------------------------------------------------------------- bool generate_ZC_sig(const crypto::hash& tx_hash_for_signature, size_t input_index, const tx_source_entry& se, const input_generation_context_data& in_context, - const account_keys& sender_account_keys, const crypto::scalar_t& blinding_masks_sum, const uint64_t tx_flags, crypto::scalar_t& local_blinding_masks_sum, transaction& tx, bool last_output) + const account_keys& sender_account_keys, const uint64_t tx_flags, outputs_generation_context& ogc, transaction& tx, bool last_output) { bool watch_only_mode = sender_account_keys.spend_secret_key == null_skey; - CHECK_AND_ASSERT_MES(se.is_zarcanum(), false, "sources contains a non-zarcanum input"); + CHECK_AND_ASSERT_MES(se.is_zc(), false, "sources contains a non-zc input"); CHECK_AND_ASSERT_MES(input_index < tx.vin.size(), false, "input_index (" << input_index << ") is out-of-bounds, vin.size = " << tx.vin.size()); CHECK_AND_ASSERT_MES(tx.vin[input_index].type() == typeid(txin_zc_input), false, "Unexpected type of input #" << input_index); @@ -1616,30 +1856,47 @@ namespace currency if (watch_only_mode) return true; // in this mode just append empty signatures + crypto::point_t asset_id_pt(se.asset_id); + crypto::point_t source_blinded_asset_id = asset_id_pt + se.real_out_asset_id_blinding_mask * crypto::c_point_X; // T_i = H_i + r_i * X + CHECK_AND_ASSERT_MES(crypto::point_t(in_context.outputs[in_context.real_out_index].blinded_asset_id).modify_mul8() == source_blinded_asset_id, false, "real output blinded asset id check failed"); + ogc.real_zc_ins_asset_ids.emplace_back(asset_id_pt); + #ifndef NDEBUG { - crypto::point_t source_amount_commitment = crypto::c_scalar_1div8 * se.amount * crypto::c_point_H + crypto::c_scalar_1div8 * se.real_out_amount_blinding_mask * crypto::c_point_G; - CHECK_AND_ASSERT_MES(in_context.outputs[in_context.real_out_index].amount_commitment == source_amount_commitment.to_public_key(), false, "real output amount commitment check failed"); + crypto::point_t source_amount_commitment = se.amount * source_blinded_asset_id + se.real_out_amount_blinding_mask * crypto::c_point_G; + CHECK_AND_ASSERT_MES(crypto::point_t(in_context.outputs[in_context.real_out_index].amount_commitment).modify_mul8() == source_amount_commitment, false, "real output amount commitment check failed"); } #endif - crypto::scalar_t blinding_mask = 0; + crypto::scalar_t pseudo_out_amount_blinding_mask = 0; + crypto::scalar_t pseudo_out_asset_id_blinding_mask = crypto::scalar_t::random(); if ((last_output && (tx_flags & TX_FLAG_SIGNATURE_MODE_SEPARATE) == 0) || se.separately_signed_tx_complete) { // either normal tx or the last signature of consolidated tx -- in both cases we need to calculate non-random blinding mask for pseudo output commitment - blinding_mask = blinding_masks_sum + local_blinding_masks_sum; - // @#@ TODO additional check for the last iteration ? + pseudo_out_amount_blinding_mask = ogc.amount_blinding_masks_sum - ogc.pseudo_out_amount_blinding_masks_sum - ogc.ao_amount_blinding_mask; // A_1 - A^p_0 = (f_1 - f'_1) * G => f'_{i-1} = sum{y_j} - sum{f'_i} } else { - blinding_mask.make_random(); - local_blinding_masks_sum -= blinding_mask; // pseudo out masks are taken into account with negative sign + pseudo_out_amount_blinding_mask.make_random(); + ogc.pseudo_out_amount_blinding_masks_sum += pseudo_out_amount_blinding_mask; } - crypto::point_t pseudo_out_amount_commitment = se.amount * crypto::c_point_H + blinding_mask * crypto::c_point_G; - sig.pseudo_out_amount_commitment = (crypto::c_scalar_1div8 * pseudo_out_amount_commitment).to_public_key(); + crypto::point_t pseudo_out_blinded_asset_id = source_blinded_asset_id + pseudo_out_asset_id_blinding_mask * crypto::c_point_X; // T^p_i = T_i + r'_i * X + sig.pseudo_out_blinded_asset_id = (crypto::c_scalar_1div8 * pseudo_out_blinded_asset_id).to_public_key(); + ogc.real_in_asset_id_blinding_mask_x_amount_sum += se.real_out_asset_id_blinding_mask * se.amount; // += r_i * a_i + ogc.pseudo_outs_blinded_asset_ids.emplace_back(pseudo_out_blinded_asset_id); + ogc.pseudo_outs_plus_real_out_blinding_masks.emplace_back(pseudo_out_asset_id_blinding_mask + se.real_out_asset_id_blinding_mask); - // = two-layers ring signature data outline = + crypto::point_t pseudo_out_amount_commitment = se.amount * source_blinded_asset_id + pseudo_out_amount_blinding_mask * crypto::c_point_G; // A^p_i = a_i * T_i + f'_i * G + sig.pseudo_out_amount_commitment = (crypto::c_scalar_1div8 * pseudo_out_amount_commitment).to_public_key(); + ogc.pseudo_out_amount_commitments_sum += pseudo_out_amount_commitment; + + //LOG_PRINT_CYAN("SBAID " << ": " << asset_id_pt << " + " << se.real_out_asset_id_blinding_mask << " x X", LOG_LEVEL_0); + //LOG_PRINT_CYAN(" == " << source_blinded_asset_id, LOG_LEVEL_0); + //LOG_PRINT_CYAN("POAM " << ": " << crypto::scalar_t(se.amount) << " x " << source_blinded_asset_id << " + " << pseudo_out_amount_blinding_mask << " x G", LOG_LEVEL_0); + //LOG_PRINT_CYAN(" == " << pseudo_out_amount_commitment << ", x 1/8 == " << sig.pseudo_out_amount_commitment, LOG_LEVEL_0); + + // = three-layers ring signature data outline = // (j in [0, ring_size-1]) // layer 0 ring // se.outputs[j].stealth_address; @@ -1651,13 +1908,20 @@ namespace currency // layer 1 ring // crypto::point_t(se.outputs[j].amount_commitment) - pseudo_out_amount_commitment; // layer 1 secret (with respect to G) - // se.real_out_amount_blinding_mask - blinding_mask; + // se.real_out_amount_blinding_mask - pseudo_out_amount_blinding_mask; + // + // layer 2 ring + // crypto::point_t(se.outputs[j].blinded_asset_id) - pseudo_out_asset_id; + // layer 2 secret (with respect to X) + // -pseudo_out_asset_id_blinding_mask; - std::vector ring; + std::vector ring; for(size_t j = 0; j < in_context.outputs.size(); ++j) - ring.emplace_back(in_context.outputs[j].stealth_address, in_context.outputs[j].amount_commitment); + ring.emplace_back(in_context.outputs[j].stealth_address, in_context.outputs[j].amount_commitment, in_context.outputs[j].blinded_asset_id); - return crypto::generate_CLSAG_GG(tx_hash_for_signature, ring, pseudo_out_amount_commitment, in.k_image, in_context.in_ephemeral.sec, se.real_out_amount_blinding_mask - blinding_mask, in_context.real_out_index, sig.clsags_gg); + return crypto::generate_CLSAG_GGX(tx_hash_for_signature, ring, pseudo_out_amount_commitment, pseudo_out_blinded_asset_id, in.k_image, in_context.in_ephemeral.sec, + se.real_out_amount_blinding_mask - pseudo_out_amount_blinding_mask, + -pseudo_out_asset_id_blinding_mask, in_context.real_out_index, sig.clsags_ggx); } //-------------------------------------------------------------------------------- bool generate_NLSAG_sig(const crypto::hash& tx_hash_for_signature, const crypto::hash& tx_prefix_hash, size_t input_index, const tx_source_entry& src_entr, @@ -1714,10 +1978,21 @@ namespace currency return true; } - - crypto::hash get_asset_id_from_descriptor(const asset_descriptor_base& adb) +#define CRYPTO_HASH_ASSET_ID_ITERATIONS 1024 + void calculate_asset_id(const crypto::public_key& asset_owner, crypto::point_t* p_result_point, crypto::public_key* p_result_pub_key) { - return get_hash_from_POD_objects(CRYPTO_HDS_ASSET_ID, adb.owner); + crypto::hash h = get_hash_from_POD_objects(CRYPTO_HDS_ASSET_ID, asset_owner); + + // this hash function needs to be computationally expensive (s.e. the whitepaper) + for(uint64_t i = 0; i < CRYPTO_HASH_ASSET_ID_ITERATIONS; ++i) + h = get_hash_from_POD_objects(CRYPTO_HDS_ASSET_ID, h, i); + + crypto::point_t local_point{}; + if (!p_result_point) + p_result_point = &local_point; + *p_result_point = crypto::hash_helper_t::hp(&h, sizeof h); + if (p_result_pub_key) + p_result_point->to_public_key(*p_result_pub_key); } @@ -1736,7 +2011,7 @@ namespace currency bool r = false; transaction& tx = result.tx; - crypto::secret_key& one_time_secret_key = result.one_time_key; + crypto::secret_key& one_time_tx_secret_key = result.one_time_key; result.ftp = ftp; CHECK_AND_ASSERT_MES(destinations.size() <= CURRENCY_TX_MAX_ALLOWED_OUTS, false, "Too many outs (" << destinations.size() << ")! Tx can't be constructed."); @@ -1767,7 +2042,7 @@ namespace currency txkey = keypair::generate(); add_tx_pub_key_to_extra(tx, txkey.pub); - one_time_secret_key = txkey.sec; + one_time_tx_secret_key = txkey.sec; //add flags etc_tx_flags16_t e = AUTO_VAL_INIT(e); @@ -1781,7 +2056,7 @@ namespace currency else { txkey.pub = get_tx_pub_key_from_extra(tx); - txkey.sec = one_time_secret_key; + txkey.sec = one_time_tx_secret_key; CHECK_AND_ASSERT_MES(txkey.pub != null_pkey && txkey.sec != null_skey, false, "In append mode both public and secret keys must be provided"); //separately encrypt attachments without putting extra @@ -1803,34 +2078,17 @@ namespace currency } - uint64_t summary_inputs_money = 0; - - crypto::hash asset_id_for_destinations = currency::null_hash; - asset_descriptor_operation* pado = nullptr; - if (tx.version > TRANSACTION_VERSION_PRE_HF4) - { - pado = get_type_in_variant_container(tx.extra); - if (pado) - { - crypto::secret_key stub = AUTO_VAL_INIT(stub); - bool r = derive_key_pair_from_key_pair(sender_account_keys.account_address.spend_public_key, one_time_secret_key, stub, pado->descriptor.owner, CRYPTO_HDS_ASSET_CONTROL_KEY); - CHECK_AND_ASSERT_MES(r, false, "Failed to derive_public_key_from_tx_and_account_pub_key()"); - //also assign this asset id to destinations - asset_id_for_destinations = get_asset_id_from_descriptor(pado->descriptor); - } - } - - + // + // INs + // + uint64_t native_coins_input_sum = 0; std::vector in_contexts; - - //we'll aggregate Zarcanum outs into one txin_zarcanum_inputs - //txin_zarcanum_inputs ins_zc = AUTO_VAL_INIT(ins_zc); std::vector inputs_mapping; size_t current_index = 0; inputs_mapping.resize(sources.size()); size_t input_starter_index = tx.vin.size(); - bool has_zc_inputs = false; - //fill inputs NLSAG and Zarcanum + size_t zc_inputs_count = 0; + bool all_inputs_are_obviously_native_coins = true; for (const tx_source_entry& src_entr : sources) { inputs_mapping[current_index] = current_index; @@ -1838,12 +2096,13 @@ namespace currency in_contexts.push_back(input_generation_context_data{}); input_generation_context_data& in_context = in_contexts.back(); + // sort src_entr.outputs entries by global out index, put it to in_context.outputs, convert to relative gindices, and put new real out index into in_context.real_out_index in_context.outputs = prepare_outputs_entries_for_key_offsets(src_entr.outputs, src_entr.real_output, in_context.real_out_index); if(src_entr.is_multisig()) {//multisig input txin_multisig input_multisig = AUTO_VAL_INIT(input_multisig); - summary_inputs_money += input_multisig.amount = src_entr.amount; + input_multisig.amount = src_entr.amount; input_multisig.multisig_out_id = src_entr.multisig_id; input_multisig.sigs_count = src_entr.ms_sigs_count; tx.vin.push_back(input_multisig); @@ -1858,7 +2117,6 @@ namespace currency LOG_ERROR("htlc in: wrong output src_entr.outputs.size() = " << src_entr.outputs.size()); return false; } - summary_inputs_money += src_entr.amount; //key_derivation recv_derivation; crypto::key_image img; @@ -1889,9 +2147,6 @@ namespace currency CHECK_AND_ASSERT_MES(in_context.real_out_index < in_context.outputs.size(), false, "real_output index (" << in_context.real_out_index << ") greater than or equal to in_context.outputs.size()=" << in_context.outputs.size()); - summary_inputs_money += src_entr.amount; - - //key_derivation recv_derivation; crypto::key_image img; if (!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_context.in_ephemeral, img)) return false; @@ -1912,19 +2167,13 @@ namespace currency //TODO: Might need some refactoring since this scheme is not the clearest one(did it this way for now to keep less changes to not broke anything) //potentially this approach might help to support htlc and multisig without making to complicated code - if (src_entr.is_zarcanum()) + if (src_entr.is_zc()) { - has_zc_inputs = true; + ++zc_inputs_count; txin_zc_input zc_in = AUTO_VAL_INIT(zc_in); zc_in.k_image = img; zc_in.key_offsets = std::move(key_offsets); - //TEMPORARY - if (src_entr.asset_id != currency::null_hash) - { - zc_in.etc_details.push_back(open_asset_id{ src_entr.asset_id }); - } tx.vin.push_back(zc_in); - } else { @@ -1935,65 +2184,101 @@ namespace currency tx.vin.push_back(input_to_key); } } - } - uint64_t amount_of_assets = 0; - std::vector shuffled_dsts(destinations); - if (asset_id_for_destinations != currency::null_hash) - { - //must be asset publication - for (auto& item : shuffled_dsts) + if (src_entr.is_native_coin()) { - if (item.asset_id == currency::ffff_hash) + native_coins_input_sum += src_entr.amount; + } + else + { + // if at least one decoy output of a ZC input has a non-explicit asset id, then we can't say that all inputs are obviously native coins + for(const tx_source_entry::output_entry& oe : in_context.outputs) { - item.asset_id = asset_id_for_destinations; - amount_of_assets += item.amount; + if (crypto::point_t(oe.blinded_asset_id).modify_mul8() != currency::native_coin_asset_id_pt) + { + all_inputs_are_obviously_native_coins = false; + break; + } } } - CHECK_AND_ASSERT_MES(pado, false, "pado is null ??"); - pado->descriptor.current_supply = amount_of_assets; - //TODO: temporary - summary_inputs_money += amount_of_assets; } + bool has_non_zc_inputs = zc_inputs_count != sources.size(); // TODO @#@# reconsider this for consilidated txs + + // + // OUTs + // + std::vector shuffled_dsts(destinations); + size_t outputs_to_be_constructed = shuffled_dsts.size(); + outputs_generation_context outs_gen_context(zc_inputs_count, outputs_to_be_constructed); // auxiliary data for each output + // ASSET oprations handling + if (tx.version > TRANSACTION_VERSION_PRE_HF4) + { + asset_descriptor_operation* pado = nullptr; + pado = get_type_in_variant_container(tx.extra); + if (pado) + { + // only operation register is supported atm + CHECK_AND_ASSERT_MES(pado->operation_type == ASSET_DESCRIPTOR_OPERATION_REGISTER, false, "unsupported asset operation: " << (int)pado->operation_type); + crypto::secret_key asset_control_key{}; + bool r = derive_key_pair_from_key_pair(sender_account_keys.account_address.spend_public_key, one_time_tx_secret_key, asset_control_key, pado->descriptor.owner, CRYPTO_HDS_ASSET_CONTROL_KEY); + CHECK_AND_ASSERT_MES(r, false, "derive_key_pair_from_key_pair failed"); + + calculate_asset_id(pado->descriptor.owner, &outs_gen_context.ao_asset_id_pt, &outs_gen_context.ao_asset_id); + + // calculate amount blinding mask + outs_gen_context.ao_amount_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_ASSET_CONTROL_ABM, asset_control_key); + + // set correct asset_id to the corresponding destination entries + uint64_t amount_of_emitted_asset = 0; + for (auto& item : shuffled_dsts) + { + if (item.asset_id == currency::ffff_pkey) + { + item.asset_id = outs_gen_context.ao_asset_id; // set calculated asset_id to the asset's outputs, if this asset is being emitted within this tx + amount_of_emitted_asset += item.amount; + } + } + pado->descriptor.current_supply = amount_of_emitted_asset; // TODO: consider setting current_supply beforehand, not setting it hear in ad-hoc manner -- sowle + + outs_gen_context.ao_amount_commitment = amount_of_emitted_asset * outs_gen_context.ao_asset_id_pt + outs_gen_context.ao_amount_blinding_mask * crypto::c_point_G; + pado->opt_amount_commitment = (crypto::c_scalar_1div8 * outs_gen_context.ao_amount_commitment).to_public_key(); + //LOG_PRINT_CYAN("AO " << ": " << crypto::scalar_t(amount_of_emitted_asset) << " x " << outs_gen_context.ao_asset_id_pt << " + " << outs_gen_context.ao_amount_blinding_mask << " x G", LOG_LEVEL_0); + //LOG_PRINT_CYAN(" == " << outs_gen_context.ao_amount_commitment << ", x 1/8 == " << pado->opt_amount_commitment.get(), LOG_LEVEL_0); + } + } // "Shuffle" outs if (shuffle) std::sort(shuffled_dsts.begin(), shuffled_dsts.end(), [](const tx_destination_entry& de1, const tx_destination_entry& de2) { return de1.amount < de2.amount; }); - // TODO: consider "Shuffle" inputs - uint64_t summary_outs_money = 0; - //fill outputs + // construct outputs + uint64_t native_coins_output_sum = 0; size_t output_index = tx.vout.size(); // in case of append mode we need to start output indexing from the last one + 1 uint64_t range_proof_start_index = output_index; std::set deriv_cache; - crypto::scalar_vec_t blinding_masks(tx.vout.size() + destinations.size()); // vector of secret blinging masks for each output. For range proof generation - crypto::scalar_vec_t amounts(tx.vout.size() + destinations.size()); // vector of amounts, converted to scalars. For ranage proof generation - crypto::scalar_t blinding_masks_sum = 0; - for(const tx_destination_entry& dst_entr : shuffled_dsts) + for(size_t j = 0; j < outputs_to_be_constructed; ++j, ++output_index) { + tx_destination_entry& dst_entr = shuffled_dsts[j]; + if (all_inputs_are_obviously_native_coins && outs_gen_context.ao_asset_id == currency::null_pkey) + dst_entr.explicit_native_asset_id = true; // all inputs are obviously native coins -- all outputs must have explicit asset ids (unless there's an asset emission) + CHECK_AND_ASSERT_MES(dst_entr.amount > 0, false, "Destination with wrong amount: " << dst_entr.amount); // <<-- TODO @#@# consider removing this check - r = construct_tx_out(dst_entr, txkey.sec, output_index, tx, deriv_cache, sender_account_keys, blinding_masks[output_index], result, tx_outs_attr); + r = construct_tx_out(dst_entr, txkey.sec, output_index, tx, deriv_cache, sender_account_keys, + outs_gen_context.asset_id_blinding_masks[j], outs_gen_context.amount_blinding_masks[j], + outs_gen_context.blinded_asset_ids[j], outs_gen_context.amount_commitments[j], result, tx_outs_attr); CHECK_AND_ASSERT_MES(r, false, "Failed to construct tx out"); - amounts[output_index - range_proof_start_index] = dst_entr.amount; - summary_outs_money += dst_entr.amount; - blinding_masks_sum += blinding_masks[output_index]; - output_index++; + outs_gen_context.amounts[j] = dst_entr.amount; + outs_gen_context.asset_ids[j] = crypto::point_t(dst_entr.asset_id); + outs_gen_context.asset_id_blinding_mask_x_amount_sum += outs_gen_context.asset_id_blinding_masks[j] * dst_entr.amount; + outs_gen_context.amount_blinding_masks_sum += outs_gen_context.amount_blinding_masks[j]; + outs_gen_context.amount_commitments_sum += outs_gen_context.amount_commitments[j]; + if (dst_entr.is_native_coin()) + native_coins_output_sum += dst_entr.amount; } - //check money - if (!(flags&TX_FLAG_SIGNATURE_MODE_SEPARATE)) - { - if (summary_outs_money > summary_inputs_money) - { - LOG_ERROR("Transaction inputs money (" << print_money_brief(summary_inputs_money) << ") is less than outputs money (" << print_money_brief(summary_outs_money) << ")"); - return false; - } - } - - //process offers and put there offers derived keys uint64_t att_count = 0; for (auto& o : tx.attachment) @@ -2005,7 +2290,7 @@ namespace currency { CHECK_AND_ASSERT_MES(tsa.security.size() == 1, false, "Wrong tsa.security.size() = " << tsa.security.size()); - r = derive_public_key_from_target_address(sender_account_keys.account_address, one_time_secret_key, att_count, tsa.security.back()); + r = derive_public_key_from_target_address(sender_account_keys.account_address, one_time_tx_secret_key, att_count, tsa.security.back()); CHECK_AND_ASSERT_MES(r, false, "Failed to derive_public_key_from_target_address"); } att_count++; @@ -2022,27 +2307,14 @@ namespace currency //2 sort the inputs in given range std::sort(tx.vin.begin() + input_starter_index, tx.vin.end(), less_txin_v); - } - - if (tx.version > TRANSACTION_VERSION_PRE_HF4) - { - // add range proofs - currency::zc_outs_range_proof range_proofs = AUTO_VAL_INIT(range_proofs); - bool r = generate_zc_outs_range_proof(range_proof_start_index, amounts.size(), amounts, blinding_masks, tx.vout, range_proofs); - CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); - tx.attachment.push_back(range_proofs); // add explicit fee info - r = add_tx_fee_amount_to_extra(tx, summary_inputs_money - summary_outs_money); + r = add_tx_fee_amount_to_extra(tx, native_coins_input_sum - native_coins_output_sum); CHECK_AND_ASSERT_MES(r, false, "add_tx_fee_amount_to_extra failed"); - - if (!has_zc_inputs) - { - r = generate_tx_balance_proof(tx, blinding_masks_sum, amount_of_assets); - CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); - } } + // attachments container should be sealed by now + if (flags & TX_FLAG_SIGNATURE_MODE_SEPARATE) { // for separately signed tx each input has to contain information about corresponding outputs, extra entries and attachments @@ -2064,14 +2336,15 @@ namespace currency if (tx.attachment.size()) add_attachments_info_to_extra(tx.extra, tx.attachment); } + + // - // generate ring signatures + // 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, tx_prefix_hash); - //size_t input_index = input_starter_index; - //size_t in_context_index = 0; - crypto::scalar_t local_blinding_masks_sum = 0; // ZC only + crypto::hash tx_prefix_hash = get_transaction_prefix_hash(tx); + + // ring signatures (per-input proofs) r = false; for (size_t i_ = 0; i_ != sources.size(); i_++) { @@ -2082,12 +2355,11 @@ namespace currency CHECK_AND_ASSERT_MES(tx_hash_for_signature != null_hash, false, "prepare_prefix_hash_for_sign failed"); std::stringstream ss_ring_s; - if (source_entry.is_zarcanum()) + if (source_entry.is_zc()) { // ZC // blinding_masks_sum is supposed to be sum(mask of all tx output) - sum(masks of all pseudo out commitments) - r = generate_ZC_sig(tx_hash_for_signature, i_ + input_starter_index, source_entry, in_contexts[i_mapped], sender_account_keys, blinding_masks_sum, flags, - local_blinding_masks_sum, tx, i_ + 1 == sources.size()); + r = generate_ZC_sig(tx_hash_for_signature, i_ + input_starter_index, source_entry, in_contexts[i_mapped], sender_account_keys, flags, outs_gen_context, tx, i_ + 1 == sources.size()); CHECK_AND_ASSERT_MES(r, false, "generate_ZC_sigs failed"); } else @@ -2100,6 +2372,42 @@ namespace currency LOG_PRINT2("construct_tx.log", "transaction_created: " << get_transaction_hash(tx) << ENDL << obj_to_json_str(tx) << ENDL << ss_ring_s.str(), LOG_LEVEL_3); } + // + // proofs (transaction-wise, not pre-input) + // + if (tx.version > TRANSACTION_VERSION_PRE_HF4) + { + // asset surjection proof + currency::zc_asset_surjection_proof asp{}; + bool r = generate_asset_surjection_proof(tx_prefix_hash, has_non_zc_inputs, outs_gen_context, asp); + CHECK_AND_ASSERT_MES(r, false, "generete_asset_surjection_proof failed"); + tx.proofs.emplace_back(std::move(asp)); + + // range proofs + currency::zc_outs_range_proof range_proofs{}; + r = generate_zc_outs_range_proof(tx_prefix_hash, range_proof_start_index, outs_gen_context, tx.vout, range_proofs); + CHECK_AND_ASSERT_MES(r, false, "Failed to generate zc_outs_range_proof()"); + tx.proofs.emplace_back(std::move(range_proofs)); + + // balance proof + currency::zc_balance_proof balance_proof{}; + r = generate_tx_balance_proof(tx, tx_prefix_hash, outs_gen_context, 0, balance_proof); + CHECK_AND_ASSERT_MES(r, false, "generate_tx_balance_proof failed"); + tx.proofs.emplace_back(std::move(balance_proof)); + + // asset operation proof (if necessary) + if (outs_gen_context.ao_asset_id != currency::null_pkey) + { + // construct the asset operation proof + // TODO @#@# add support for hidden supply + crypto::signature aop_g_sig{}; + crypto::generate_signature(tx_prefix_hash, crypto::point_t(outs_gen_context.ao_amount_blinding_mask * crypto::c_point_G).to_public_key(), outs_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)); + } + } + //size_t prefix_size = get_object_blobsize(static_cast(tx)); //size_t full_blob_size = t_serializable_object_to_blob(tx).size(); //size_t estimated_blob_size = get_object_blobsize(tx); @@ -2378,12 +2686,16 @@ namespace currency VARIANT_SWITCH_END(); } VARIANT_CASE_CONST(tx_out_zarcanum, o) - if (!check_key(o.amount_commitment)) + { + if (!check_key(o.stealth_address)) return false; if (!check_key(o.concealing_point)) return false; - if (!check_key(o.stealth_address)) + if (!check_key(o.amount_commitment)) return false; + if (!check_key(o.blinded_asset_id)) + return false; + } VARIANT_SWITCH_END(); } return true; @@ -2471,6 +2783,7 @@ namespace currency return res; } //--------------------------------------------------------------- + // out_key.key =?= Hs(derivation || output_index) * G + addr.spend_public_key bool is_out_to_acc(const account_public_address& addr, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index) { crypto::public_key pk; @@ -2490,7 +2803,8 @@ namespace currency return true; } - bool is_out_to_acc(const account_public_address& addr, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount, crypto::scalar_t& blinding_mask) + bool is_out_to_acc(const account_public_address& addr, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount, crypto::public_key& decoded_asset_id, + crypto::scalar_t& amount_blinding_mask, crypto::scalar_t& asset_id_blinding_mask) { crypto::scalar_t h; // = crypto::hash_helper_t::hs(reinterpret_cast(derivation), output_index); // h = Hs(8 * r * V, i) crypto::derivation_to_scalar(derivation, output_index, h.as_secret_key()); // h = Hs(8 * r * V, i) @@ -2506,23 +2820,35 @@ namespace currency crypto::scalar_t amount_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_MASK, h); decoded_amount = zo.encrypted_amount ^ amount_mask.m_u64[0]; - blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_BLINDING_MASK, h); // f = Hs(domain_sep, h) + amount_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_AMOUNT_BLINDING_MASK, h); // f = Hs(domain_sep, h) - crypto::point_t A_prime; - A_prime.assign_mul_plus_G(decoded_amount, crypto::c_point_H, blinding_mask); // A' * 8 =? a * H + f * G + crypto::point_t blinded_asset_id = crypto::point_t(zo.blinded_asset_id).modify_mul8(); + crypto::point_t A_prime = decoded_amount * blinded_asset_id + amount_blinding_mask * crypto::c_point_G; // A' * 8 =? a * T + f * G if (A_prime != crypto::point_t(zo.amount_commitment).modify_mul8()) return false; + if (blinded_asset_id == currency::native_coin_asset_id_pt) + { + asset_id_blinding_mask = 0; + decoded_asset_id = currency::native_coin_asset_id; + } + else + { + asset_id_blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_ASSET_BLINDING_MASK, h); // f = Hs(domain_sep, d, i) + crypto::point_t asset_id = blinded_asset_id - asset_id_blinding_mask * crypto::c_point_X; // H = T - s * X + decoded_asset_id = asset_id.to_public_key(); + } + return true; - } + } //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation) + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& sum_of_native_outs, crypto::key_derivation& derivation) { crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); if (null_pkey == tx_pub_key) return false; - return lookup_acc_outs(acc, tx, get_tx_pub_key_from_extra(tx), outs, money_transfered, derivation); + return lookup_acc_outs(acc, tx, get_tx_pub_key_from_extra(tx), outs, sum_of_native_outs, derivation); } //--------------------------------------------------------------- bool check_tx_derivation_hint(const transaction& tx, const crypto::key_derivation& derivation) @@ -2571,22 +2897,22 @@ namespace currency return true; } //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation) + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& sum_of_native_outs, crypto::key_derivation& derivation) { std::list htlc_info_list; - return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered, derivation, htlc_info_list); + return lookup_acc_outs(acc, tx, tx_pub_key, outs, sum_of_native_outs, derivation, htlc_info_list); } //--------------------------------------------------------------- - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation, std::list& htlc_info_list) + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& sum_of_native_outs, crypto::key_derivation& derivation, std::list& htlc_info_list) { - money_transfered = 0; + sum_of_native_outs = 0; bool r = generate_key_derivation(tx_pub_key, acc.view_secret_key, derivation); CHECK_AND_ASSERT_MES(r, false, "unable to generate derivation from tx_pub = " << tx_pub_key << " * view_sec, invalid tx_pub?"); if (is_coinbase(tx) && get_block_height(tx) == 0 && tx_pub_key == ggenesis_tx_pub_key) { //genesis coinbase - return lookup_acc_outs_genesis(acc, tx, tx_pub_key, outs, money_transfered, derivation); + return lookup_acc_outs_genesis(acc, tx, tx_pub_key, outs, sum_of_native_outs, derivation); } if (!check_tx_derivation_hint(tx, derivation)) @@ -2603,13 +2929,13 @@ namespace currency if (is_out_to_acc(acc.account_address, t, derivation, output_index)) { outs.emplace_back(output_index, o.amount); - money_transfered += o.amount; + sum_of_native_outs += o.amount; } VARIANT_CASE_CONST(txout_multisig, t) if (is_out_to_acc(acc.account_address, t, derivation, output_index)) { outs.emplace_back(output_index, o.amount); // TODO: @#@# consider this - //don't cout this money + //don't cout this money in sum_of_native_outs } VARIANT_CASE_CONST(txout_htlc, htlc) htlc_info hi = AUTO_VAL_INIT(hi); @@ -2623,6 +2949,11 @@ namespace currency hi.hltc_our_out_is_before_expiration = false; htlc_info_list.push_back(hi); } + else + { + LOG_ERROR("lookup_acc_outs: handling txout_htlc went wrong, output_index: " << output_index); + return false; + } outs.emplace_back(output_index, o.amount); VARIANT_CASE_OTHER() LOG_ERROR("Wrong type at lookup_acc_outs, unexpected type is: " << o.target.type().name()); @@ -2630,18 +2961,19 @@ namespace currency VARIANT_SWITCH_END(); } VARIANT_CASE_CONST(tx_out_zarcanum, zo) + { uint64_t amount = 0; - crypto::scalar_t blinding_mask = 0; - if (is_out_to_acc(acc.account_address, zo, derivation, output_index, amount, blinding_mask)) + crypto::public_key asset_id{}; + crypto::scalar_t amount_blinding_mask = 0, asset_id_blinding_mask = 0; + if (is_out_to_acc(acc.account_address, zo, derivation, output_index, amount, asset_id, amount_blinding_mask, asset_id_blinding_mask)) { - outs.emplace_back(output_index, amount, blinding_mask); - open_asset_id v = AUTO_VAL_INIT(v); - if (get_type_in_variant_container(zo.etc_details, v)) - { - outs.back().asset_id = v.asset_id; - } - money_transfered += amount; + crypto::point_t asset_id_pt = crypto::point_t(zo.blinded_asset_id).modify_mul8() - asset_id_blinding_mask * crypto::c_point_X; + crypto::public_key asset_id = asset_id_pt.to_public_key(); + outs.emplace_back(output_index, amount, amount_blinding_mask, asset_id_blinding_mask, asset_id); + if (asset_id == currency::native_coin_asset_id) + sum_of_native_outs += amount; } + } VARIANT_SWITCH_END(); output_index++; } @@ -3379,7 +3711,8 @@ namespace currency bool operator()(const zc_outs_range_proof& rp) { tv.type = "zc_outs_range_proof"; - tv.short_view = "outputs_count = " + std::to_string(rp.outputs_count); + // TODO @#@# + //tv.short_view = "outputs_count = " + std::to_string(rp.outputs_count); return true; } bool operator()(const zc_balance_proof& bp) @@ -3965,18 +4298,21 @@ namespace currency bool operator ==(const currency::void_sig& a, const currency::void_sig& b) { //@#@ + ASSERT_MES_AND_THROW("not implemented yet"); return false; } //-------------------------------------------------------------------------------- bool operator ==(const currency::ZC_sig& a, const currency::ZC_sig& b) { //@#@ TODO + ASSERT_MES_AND_THROW("not implemented yet"); return false; } //-------------------------------------------------------------------------------- bool operator ==(const currency::zarcanum_sig& a, const currency::zarcanum_sig& b) { //@#@ TODO + ASSERT_MES_AND_THROW("not implemented yet"); return false; } //-------------------------------------------------------------------------------- @@ -3985,23 +4321,6 @@ namespace currency return a.n == b.n && a.tx_id == b.tx_id; } //-------------------------------------------------------------------------------- - bool verify_multiple_zc_outs_range_proofs(const std::vector& range_proofs) - { - if (range_proofs.empty()) - return true; - - std::vector sigs; - sigs.reserve(range_proofs.size()); - for(auto& el : range_proofs) - sigs.emplace_back(el.range_proof.bpp, el.amount_commitments); - - uint8_t err = 0; - bool r = crypto::bpp_verify<>(sigs, &err); - CHECK_AND_ASSERT_MES(r, false, "bpp_verify failed with error " << (int)err); - - return true; - } - //-------------------------------------------------------------------------------- boost::multiprecision::uint1024_t get_a_to_b_relative_cumulative_difficulty(const wide_difficulty_type& difficulty_pos_at_split_point, diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index db3f690a..9ffbf6f3 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -53,15 +53,6 @@ namespace currency { - bool operator ==(const currency::transaction& a, const currency::transaction& b); - bool operator ==(const currency::block& a, const currency::block& b); - bool operator ==(const currency::extra_attachment_info& a, const currency::extra_attachment_info& b); - bool operator ==(const currency::NLSAG_sig& a, const currency::NLSAG_sig& b); - bool operator ==(const currency::void_sig& a, const currency::void_sig& b); - bool operator ==(const currency::ZC_sig& a, const currency::ZC_sig& b); - bool operator ==(const currency::zarcanum_sig& a, const currency::zarcanum_sig& b); - bool operator ==(const currency::ref_by_id& a, const currency::ref_by_id& b); - typedef boost::multiprecision::uint128_t uint128_tl; @@ -205,19 +196,25 @@ namespace currency : index(index) , amount(amount) {} - wallet_out_info(size_t index, uint64_t amount, const crypto::scalar_t& blinding_mask) + wallet_out_info(size_t index, uint64_t amount, const crypto::scalar_t& amount_blinding_mask, const crypto::scalar_t& asset_id_blinding_mask, const crypto::public_key& asset_id) : index(index) , amount(amount) - , blinding_mask(blinding_mask) + , amount_blinding_mask(amount_blinding_mask) + , asset_id_blinding_mask(asset_id_blinding_mask) + , asset_id(asset_id) {} - size_t index = SIZE_MAX; - uint64_t amount = 0; - crypto::scalar_t blinding_mask = 0; - crypto::hash asset_id = currency::null_hash; + size_t index = SIZE_MAX; + uint64_t amount = 0; + crypto::scalar_t amount_blinding_mask = 0; + crypto::scalar_t asset_id_blinding_mask = 0; + crypto::public_key asset_id = currency::native_coin_asset_id; // use point_t instead as this is for internal use only? + + bool is_native_coin() const { return asset_id == currency::native_coin_asset_id; } }; + // TODO @#@# consider refactoring to eliminate redundant coping and to imporve performance struct zc_outs_range_proofs_with_commitments { zc_outs_range_proofs_with_commitments(const zc_outs_range_proof& range_proof, const std::vector& amount_commitments) @@ -232,7 +229,14 @@ namespace currency }; bool verify_multiple_zc_outs_range_proofs(const std::vector& range_proofs); - bool check_tx_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); + bool generate_asset_surjection_proof(const crypto::hash& context_hash, bool has_non_zc_inputs, outputs_generation_context& ogc, zc_asset_surjection_proof& result); + bool verify_asset_surjection_proof(const transaction& tx, const crypto::hash& tx_id); + bool generate_tx_balance_proof(const transaction &tx, const crypto::hash& tx_id, const outputs_generation_context& ogc, uint64_t block_reward_for_miner_tx, zc_balance_proof& proof); + bool generate_zc_outs_range_proof(const crypto::hash& context_hash, size_t out_index_start, const outputs_generation_context& outs_gen_context, + const std::vector& vouts, zc_outs_range_proof& result); + bool check_tx_bare_balance(const transaction& tx, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); + bool check_tx_balance(const transaction& tx, const crypto::hash& tx_id, uint64_t additional_inputs_amount_and_fees_for_mining_tx = 0); + bool validate_asset_operation(const transaction& tx, const crypto::hash& tx_id, const asset_descriptor_operation& ado, crypto::public_key& asset_id); //--------------------------------------------------------------- bool construct_miner_tx(size_t height, size_t median_size, const boost::multiprecision::uint128_t& already_generated_coins, size_t current_block_size, @@ -241,16 +245,17 @@ namespace currency const account_public_address &stakeholder_address, transaction& tx, uint64_t tx_version, - const blobdata& extra_nonce = blobdata(), - size_t max_outs = CURRENCY_MINER_TX_MAX_OUTS, - bool pos = false, - const pos_entry& pe = pos_entry(), - crypto::scalar_t* blinding_masks_sum_ptr = nullptr, - const keypair* tx_one_time_key_to_use = nullptr); + const blobdata& extra_nonce = blobdata(), + size_t max_outs = CURRENCY_MINER_TX_MAX_OUTS, + bool pos = false, + const pos_entry& pe = pos_entry(), + outputs_generation_context* ogc_ptr = nullptr, + const keypair* tx_one_time_key_to_use = nullptr); //--------------------------------------------------------------- uint64_t get_string_uint64_hash(const std::string& str); - bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, const account_keys& self, crypto::scalar_t& out_blinding_mask, finalized_tx& result, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED); + bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, const account_keys& self, crypto::scalar_t& asset_blinding_mask, crypto::scalar_t& amount_blinding_mask, crypto::point_t& blinded_asset_id, crypto::point_t& amount_commitment, finalized_tx& result, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED); bool construct_tx_out(const tx_destination_entry& de, const crypto::secret_key& tx_sec_key, size_t output_index, transaction& tx, std::set& deriv_cache, const account_keys& self, uint8_t tx_outs_attr = CURRENCY_TO_KEY_OUT_RELAXED); + bool validate_alias_name(const std::string& al); bool validate_password(const std::string& password); void get_attachment_extra_info_details(const std::vector& attachment, extra_attachment_info& eai); @@ -293,7 +298,8 @@ namespace currency uint64_t get_tx_version(uint64_t tx_expected_block_height, const hard_forks_descriptor& hfd); // returns tx version based on the height of the block where the transaction is expected to be bool construct_tx(const account_keys& sender_account_keys, const finalize_tx_param& param, finalized_tx& result); - crypto::hash get_asset_id_from_descriptor(const asset_descriptor_base& adb); + void calculate_asset_id(const crypto::public_key& asset_owner, crypto::point_t* p_result_point, crypto::public_key* p_result_pub_key); + bool sign_multisig_input_in_tx(currency::transaction& tx, size_t ms_input_index, const currency::account_keys& keys, const currency::transaction& source_tx, bool *p_is_input_fully_signed = nullptr); @@ -311,10 +317,10 @@ namespace currency crypto::hash get_multisig_out_id(const transaction& tx, size_t n); bool is_out_to_acc(const account_public_address& addr, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index); bool is_out_to_acc(const account_public_address& addr, const txout_multisig& out_multisig, const crypto::key_derivation& derivation, size_t output_index); - bool is_out_to_acc(const account_public_address& addr, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount, crypto::scalar_t& blinding_mask); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation, std::list& htlc_info_list); - bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& money_transfered, crypto::key_derivation& derivation); + bool is_out_to_acc(const account_public_address& addr, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount, crypto::public_key& decoded_asset_id, crypto::scalar_t& amount_blinding_mask, crypto::scalar_t& asset_id_blinding_mask); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& sum_of_native_outs, crypto::key_derivation& derivation); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector& outs, uint64_t& sum_of_native_outs, crypto::key_derivation& derivation, std::list& htlc_info_list); + bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector& outs, uint64_t& sum_of_native_outs, crypto::key_derivation& derivation); bool get_tx_fee(const transaction& tx, uint64_t & fee); uint64_t get_tx_fee(const transaction& tx); bool derive_ephemeral_key_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral); @@ -602,36 +608,6 @@ namespace currency return get_or_add_field_to_variant_vector(extra); } //--------------------------------------------------------------- - template - void update_or_add_field_to_extra(std::vector& variant_container, const variant_type_t& v) - { - for (auto& ev : variant_container) - { - if (ev.type() == typeid(variant_type_t)) - { - boost::get(ev) = v; - return; - } - } - variant_container.push_back(v); - } - //--------------------------------------------------------------- - template - void remove_field_of_type_from_extra(std::vector& variant_container) - { - for (size_t i = 0; i != variant_container.size();) - { - if (variant_container[i].type() == typeid(variant_type_t)) - { - variant_container.erase(variant_container.begin()+i); - } - else - { - i++; - } - } - } - //--------------------------------------------------------------- template bool get_payment_id_from_tx(const t_container& att, std::string& payment_id) { diff --git a/src/currency_core/currency_format_utils_transactions.cpp b/src/currency_core/currency_format_utils_transactions.cpp index c0e06a3b..e7071e0c 100644 --- a/src/currency_core/currency_format_utils_transactions.cpp +++ b/src/currency_core/currency_format_utils_transactions.cpp @@ -33,11 +33,6 @@ namespace currency return expiration_time <= expiration_ts_median + TX_EXPIRATION_MEDIAN_SHIFT; } //--------------------------------------------------------------- - - - - - uint64_t get_burned_amount(const transaction& tx) { uint64_t res = 0; @@ -51,7 +46,7 @@ namespace currency res += o.amount; } VARIANT_CASE_CONST(tx_out_zarcanum, o) - //@#@ + //@#@# TODO obtain info about public burn of native coins in ZC outputs VARIANT_CASE_THROW_ON_OTHER(); VARIANT_SWITCH_END(); } @@ -324,6 +319,25 @@ namespace currency return true; } //--------------------------------------------------------------- + bool is_asset_emitting_transaction(const transaction& tx, asset_descriptor_operation* p_ado /* = nullptr */) + { + if (tx.version <= TRANSACTION_VERSION_PRE_HF4) + return false; + + asset_descriptor_operation local_ado{}; + if (p_ado == nullptr) + p_ado = &local_ado; + + if (!get_type_in_variant_container(tx.extra, *p_ado)) + return false; + + // TODO @#@# change to ASSET_DESCRIPTOR_OPERATION_EMMIT ! + if (p_ado->operation_type != ASSET_DESCRIPTOR_OPERATION_REGISTER) + return false; + + return true; + } + //--------------------------------------------------------------- // Prepapres vector of output_entry to be used in key_offsets in a transaction input: // 1) sort all entries by gindex (while moving all ref_by_id to the end, keeping they relative order) // 2) convert absolute global indices to relative key_offsets diff --git a/src/currency_core/currency_format_utils_transactions.h b/src/currency_core/currency_format_utils_transactions.h index d123a54a..a2ba6080 100644 --- a/src/currency_core/currency_format_utils_transactions.h +++ b/src/currency_core/currency_format_utils_transactions.h @@ -1,4 +1,4 @@ -// Copyright (c) 2018-2022 Zano Project +// Copyright (c) 2018-2023 Zano Project // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. @@ -22,13 +22,14 @@ namespace currency output_entry(const output_entry &) = default; output_entry(const txout_ref_v& out_reference, const crypto::public_key& stealth_address) : out_reference(out_reference), stealth_address(stealth_address), concealing_point(null_pkey), amount_commitment(null_pkey) {} - output_entry(const txout_ref_v& out_reference, const crypto::public_key& stealth_address, const crypto::public_key& concealing_point, const crypto::public_key& amount_commitment) - : out_reference(out_reference), stealth_address(stealth_address), concealing_point(concealing_point), amount_commitment(amount_commitment) {} + output_entry(const txout_ref_v& out_reference, const crypto::public_key& stealth_address, const crypto::public_key& concealing_point, const crypto::public_key& amount_commitment, const crypto::public_key& blinded_asset_id) + : out_reference(out_reference), stealth_address(stealth_address), concealing_point(concealing_point), amount_commitment(amount_commitment), blinded_asset_id(blinded_asset_id) {} txout_ref_v out_reference; // either global output index or ref_by_id crypto::public_key stealth_address; // a.k.a output's one-time public key - crypto::public_key concealing_point; // only for zarcaum outputs - crypto::public_key amount_commitment; // only for zarcaum outputs + crypto::public_key concealing_point; // only for ZC outputs + crypto::public_key amount_commitment; // only for ZC outputs + crypto::public_key blinded_asset_id; // only for ZC outputs bool operator==(const output_entry& rhs) const { return out_reference == rhs.out_reference; } // used in prepare_outputs_entries_for_key_offsets, it's okay to do partially comparison @@ -37,6 +38,7 @@ namespace currency FIELD(stealth_address) FIELD(concealing_point) FIELD(amount_commitment) + FIELD(blinded_asset_id) END_SERIALIZE() }; @@ -45,7 +47,8 @@ namespace currency std::vector outputs; uint64_t real_output = 0; //index in outputs vector of real output_entry crypto::public_key real_out_tx_key = currency::null_pkey; //real output's transaction's public key - crypto::scalar_t real_out_amount_blinding_mask; //blinding mask of real out's amount committment (only for zarcanum inputs, otherwise must be 0) + crypto::scalar_t real_out_amount_blinding_mask = 0; //blinding mask of real out's amount committment (only for ZC inputs, otherwise must be 0) + crypto::scalar_t real_out_asset_id_blinding_mask = 0; //blinding mask of real out's asset_od (only for ZC inputs, otherwise must be 0) size_t real_output_in_tx_index = 0; //index in transaction outputs vector uint64_t amount = 0; //money uint64_t transfer_index = 0; //index in m_transfers @@ -54,16 +57,18 @@ namespace currency size_t ms_keys_count = 0; //if txin_multisig: must be equal to size of output's keys container bool separately_signed_tx_complete = false; //for separately signed tx only: denotes the last source entry in complete tx to explicitly mark the final step of tx creation std::string htlc_origin; //for htlc, specify origin - crypto::hash asset_id = currency::null_hash; //asset id + crypto::public_key asset_id = currency::native_coin_asset_id; //asset id (not blinded, not premultiplied by 1/8) TODO @#@# consider changing to crypto::point_t - bool is_multisig() const { return ms_sigs_count > 0; } - bool is_zarcanum() const { return !real_out_amount_blinding_mask.is_zero(); } + bool is_multisig() const { return ms_sigs_count > 0; } + bool is_zc() const { return !real_out_amount_blinding_mask.is_zero(); } + bool is_native_coin() const { return asset_id == currency::native_coin_asset_id; } BEGIN_SERIALIZE_OBJECT() FIELD(outputs) FIELD(real_output) FIELD(real_out_tx_key) FIELD(real_out_amount_blinding_mask) + FIELD(real_out_asset_id_blinding_mask) FIELD(real_output_in_tx_index) FIELD(amount) FIELD(transfer_index) @@ -91,21 +96,23 @@ namespace currency struct tx_destination_entry { - uint64_t amount = 0; //money - std::list addr; //destination address, in case of 1 address - txout_to_key, in case of more - txout_multisig - size_t minimum_sigs = 0; //if txout_multisig: minimum signatures that are required to spend this output (minimum_sigs <= addr.size()) IF txout_to_key - not used - uint64_t amount_to_provide = 0; //amount money that provided by initial creator of tx, used with partially created transactions + uint64_t amount = 0; // money + std::list addr; // destination address, in case of 1 address - txout_to_key, in case of more - txout_multisig + size_t minimum_sigs = 0; // if txout_multisig: minimum signatures that are required to spend this output (minimum_sigs <= addr.size()) IF txout_to_key - not used + uint64_t amount_to_provide = 0; // amount money that provided by initial creator of tx, used with partially created transactions uint64_t unlock_time = 0; - destination_option_htlc_out htlc_options; //htlc options - crypto::hash asset_id = currency::null_hash; - + destination_option_htlc_out htlc_options; // htlc options + crypto::public_key asset_id = currency::native_coin_asset_id; // not blinded, not premultiplied + bool explicit_native_asset_id = false; tx_destination_entry() = default; tx_destination_entry(uint64_t a, const account_public_address& ad) : amount(a), addr(1, ad) {} - tx_destination_entry(uint64_t a, const account_public_address& ad, const crypto::hash& aid) : amount(a), addr(1, ad), asset_id(aid) {} + tx_destination_entry(uint64_t a, const account_public_address& ad, const crypto::public_key& aid) : amount(a), addr(1, ad), asset_id(aid) {} tx_destination_entry(uint64_t a, const account_public_address& ad, uint64_t ut) : amount(a), addr(1, ad), unlock_time(ut) {} tx_destination_entry(uint64_t a, const std::list& addr) : amount(a), addr(addr), minimum_sigs(addr.size()){} - tx_destination_entry(uint64_t a, const std::list& addr, const crypto::hash& aid) : amount(a), addr(addr), minimum_sigs(addr.size()), asset_id(aid) {} + tx_destination_entry(uint64_t a, const std::list& addr, const crypto::public_key& aid) : amount(a), addr(addr), minimum_sigs(addr.size()), asset_id(aid) {} + + bool is_native_coin() const { return asset_id == currency::native_coin_asset_id; } BEGIN_SERIALIZE_OBJECT() @@ -116,9 +123,40 @@ namespace currency FIELD(unlock_time) FIELD(htlc_options) FIELD(asset_id) + FIELD(explicit_native_asset_id) END_SERIALIZE() }; - + //--------------------------------------------------------------- + template + void update_or_add_field_to_extra(std::vector& variant_container, const variant_type_t& v) + { + for (auto& ev : variant_container) + { + if (ev.type() == typeid(variant_type_t)) + { + boost::get(ev) = v; + return; + } + } + variant_container.push_back(v); + } + //--------------------------------------------------------------- + template + void remove_field_of_type_from_extra(std::vector& variant_container) + { + for (size_t i = 0; i != variant_container.size();) + { + if (variant_container[i].type() == typeid(variant_type_t)) + { + variant_container.erase(variant_container.begin()+i); + } + else + { + i++; + } + } + } + //--------------------------------------------------------------- template uint64_t get_tx_x_detail(const transaction& tx) { @@ -126,6 +164,7 @@ namespace currency get_type_in_variant_container(tx.extra, e); return e.v; } + //--------------------------------------------------------------- template void set_tx_x_detail(transaction& tx, uint64_t v) { @@ -133,7 +172,7 @@ namespace currency e.v = v; update_or_add_field_to_extra(tx.extra, e); } - + //--------------------------------------------------------------- uint64_t get_tx_unlock_time(const transaction& tx, uint64_t o_i); uint64_t get_tx_max_unlock_time(const transaction& tx); bool get_tx_max_min_unlock_time(const transaction& tx, uint64_t& max_unlock_time, uint64_t& min_unlock_time); @@ -162,6 +201,86 @@ namespace currency bool tx_to_blob(const transaction& b, blobdata& b_blob); bool read_keyimages_from_tx(const transaction& tx, std::list& kil); bool validate_inputs_sorting(const transaction& tx); + bool is_asset_emitting_transaction(const transaction& tx, asset_descriptor_operation* p_ado = nullptr); std::vector prepare_outputs_entries_for_key_offsets(const std::vector& outputs, size_t old_real_index, size_t& new_real_index) noexcept; -} + + + struct outputs_generation_context + { + outputs_generation_context() = default; + + outputs_generation_context(size_t zc_ins_count, size_t outs_count) + : /*pseudo_outs_blinded_asset_ids(zc_ins_count) + , */asset_ids(outs_count) + , blinded_asset_ids(outs_count) + , amount_commitments(outs_count) + , asset_id_blinding_masks(outs_count) + , amounts(outs_count) + , amount_blinding_masks(outs_count) + {} + + // TODO @#@# reconsider this check -- sowle + bool check_sizes(size_t zc_ins_count, size_t outs_count) const + { + return + pseudo_outs_blinded_asset_ids.size() == zc_ins_count && + asset_ids.size() == outs_count && + blinded_asset_ids.size() == outs_count && + amount_commitments.size() == outs_count && + asset_id_blinding_masks.size() == outs_count && + amounts.size() == outs_count && + amount_blinding_masks.size() == outs_count; + } + + // per output data + std::vector asset_ids; + std::vector blinded_asset_ids; // generate_zc_outs_range_proof + std::vector amount_commitments; // generate_zc_outs_range_proof construct_tx_out + crypto::scalar_vec_t asset_id_blinding_masks; // construct_tx_out + crypto::scalar_vec_t amounts; // generate_zc_outs_range_proof + crypto::scalar_vec_t amount_blinding_masks; // generate_zc_outs_range_proof + + // per zc input data + std::vector pseudo_outs_blinded_asset_ids; + crypto::scalar_vec_t pseudo_outs_plus_real_out_blinding_masks; // r_pi + r'_j + std::vector real_zc_ins_asset_ids; // H_i + + // common data: inputs + crypto::point_t pseudo_out_amount_commitments_sum = crypto::c_point_0; // generate_tx_balance_proof generate_ZC_sig + crypto::scalar_t pseudo_out_amount_blinding_masks_sum = 0; // generate_ZC_sig + crypto::scalar_t real_in_asset_id_blinding_mask_x_amount_sum = 0; // = sum( real_out_blinding_mask[i] * amount[i] ) generate_tx_balance_proof generate_ZC_sig + + // common data: outputs + crypto::point_t amount_commitments_sum = crypto::c_point_0; // generate_tx_balance_proof + crypto::scalar_t amount_blinding_masks_sum = 0; // construct_tx_out generate_tx_balance_proof generate_ZC_sig + crypto::scalar_t asset_id_blinding_mask_x_amount_sum = 0; // = sum( blinding_mask[j] * amount[j] ) generate_tx_balance_proof + + // data for ongoing asset operation in tx (if applicable, tx extra should contain asset_descriptor_operation) + crypto::public_key ao_asset_id {}; + crypto::point_t ao_asset_id_pt = crypto::c_point_0; + crypto::point_t ao_amount_commitment = crypto::c_point_0; + crypto::scalar_t ao_amount_blinding_mask {}; // generate_tx_balance_proof generate_ZC_sig + + // consider redesign, some data may possibly be excluded from kv serialization -- sowle + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(asset_ids); + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(blinded_asset_ids); + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(amount_commitments); + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(asset_id_blinding_masks); + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(amounts); + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(amount_blinding_masks); + KV_SERIALIZE_POD_AS_HEX_STRING(pseudo_out_amount_commitments_sum); + KV_SERIALIZE_POD_AS_HEX_STRING(pseudo_out_amount_blinding_masks_sum); + KV_SERIALIZE_POD_AS_HEX_STRING(real_in_asset_id_blinding_mask_x_amount_sum); + KV_SERIALIZE_POD_AS_HEX_STRING(amount_commitments_sum); + KV_SERIALIZE_POD_AS_HEX_STRING(amount_blinding_masks_sum); + KV_SERIALIZE_POD_AS_HEX_STRING(asset_id_blinding_mask_x_amount_sum); + KV_SERIALIZE_POD_AS_HEX_STRING(ao_asset_id); + KV_SERIALIZE_POD_AS_HEX_STRING(ao_asset_id_pt); + KV_SERIALIZE_POD_AS_HEX_STRING(ao_amount_commitment); + KV_SERIALIZE_POD_AS_HEX_STRING(ao_amount_blinding_mask); + END_KV_SERIALIZE_MAP() + }; + +} // namespace currency diff --git a/src/currency_core/pos_mining.cpp b/src/currency_core/pos_mining.cpp index a2b6237b..5bc1d973 100644 --- a/src/currency_core/pos_mining.cpp +++ b/src/currency_core/pos_mining.cpp @@ -25,7 +25,7 @@ namespace currency } void pos_mining_context::prepare_entry(uint64_t stake_amount, const crypto::key_image& stake_out_ki, const crypto::public_key& stake_source_tx_pub_key, uint64_t stake_out_in_tx_index, - const crypto::scalar_t& stake_out_blinding_mask, const crypto::secret_key& view_secret) + const crypto::scalar_t& stake_out_amount_blinding_mask, const crypto::secret_key& view_secret) { this->stake_amount = stake_amount; this->sk.kimage = stake_out_ki; @@ -41,7 +41,7 @@ namespace currency // q = Hs(domain_sep, Hs(8 * v * R, i) ) * 8 * v this->secret_q = v * 8 * crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_CONCEALING_POINT, h); - this->stake_out_blinding_mask = stake_out_blinding_mask; + this->stake_out_amount_blinding_mask = stake_out_amount_blinding_mask; } } @@ -62,7 +62,7 @@ namespace currency crypto::mp::uint512_t rhs; { PROFILE_FUNC("check_zarcanum"); - found = crypto::zarcanum_check_main_pos_inequality(this->kernel_hash, this->stake_out_blinding_mask, this->secret_q, this->last_pow_block_id_hashed, this->z_l_div_z_D, this->stake_amount, lhs, rhs); + found = crypto::zarcanum_check_main_pos_inequality(this->kernel_hash, this->stake_out_amount_blinding_mask, this->secret_q, this->last_pow_block_id_hashed, this->z_l_div_z_D, this->stake_amount, lhs, rhs); } if (found) { diff --git a/src/currency_core/pos_mining.h b/src/currency_core/pos_mining.h index 988eaf57..ae6baf03 100644 --- a/src/currency_core/pos_mining.h +++ b/src/currency_core/pos_mining.h @@ -9,15 +9,15 @@ namespace currency struct pos_mining_context { - // Zarcanum notation: - wide_difficulty_type basic_diff; // D + // Zarcanum notation: + wide_difficulty_type basic_diff; // D stake_kernel sk; - crypto::scalar_t last_pow_block_id_hashed; // f' - crypto::scalar_t secret_q; // q - boost::multiprecision::uint256_t z_l_div_z_D; // z * floor( l / (z * D) ) (max possible value (assuming z=2^64) : z * 2^252 / (z * 1) ~= 2^252) - crypto::hash kernel_hash; // h - crypto::scalar_t stake_out_blinding_mask; // f - uint64_t stake_amount; // a + crypto::scalar_t last_pow_block_id_hashed; // f' + crypto::scalar_t secret_q; // q + boost::multiprecision::uint256_t z_l_div_z_D; // z * floor( l / (z * D) ) (max possible value (assuming z=2^64) : z * 2^252 / (z * 1) ~= 2^252) + crypto::hash kernel_hash; // h + crypto::scalar_t stake_out_amount_blinding_mask; // f + uint64_t stake_amount; // a bool zarcanum; // false for pre-HF4 classic PoS with explicit amounts diff --git a/src/currency_core/tx_semantic_validation.cpp b/src/currency_core/tx_semantic_validation.cpp index 525aad37..26d95916 100644 --- a/src/currency_core/tx_semantic_validation.cpp +++ b/src/currency_core/tx_semantic_validation.cpp @@ -21,27 +21,27 @@ namespace currency //----------------------------------------------------------------------------------------------- bool check_tx_inputs_keyimages_diff(const transaction& tx) { - std::unordered_set ki; - for(const auto& in : tx.vin) + std::unordered_set key_images; + crypto::key_image ki{}; + for(const auto& in_v : tx.vin) { - if (in.type() == typeid(txin_to_key)) + if (get_key_image_from_txin_v(in_v, ki)) { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_to_key, tokey_in, false); - if (!ki.insert(tokey_in.k_image).second) - return false; - } - else if (in.type() == typeid(txin_htlc)) - { - CHECKED_GET_SPECIFIC_VARIANT(in, const txin_htlc, htlc_in, false); - if (!ki.insert(htlc_in.k_image).second) + if (!key_images.insert(ki).second) return false; } } return true; } //----------------------------------------------------------------------------------------------- - bool validate_tx_semantic(const transaction& tx, size_t tx_block_size) + bool validate_tx_semantic(const transaction& tx, size_t tx_blob_size) { + if (tx_blob_size >= CURRENCY_MAX_TRANSACTION_BLOB_SIZE) + { + LOG_PRINT_RED_L0("tx blob size is " << tx_blob_size << ", it is greater than or equal to allowed maximum of " << CURRENCY_MAX_TRANSACTION_BLOB_SIZE); + return false; + } + if (!tx.vin.size()) { LOG_PRINT_RED_L0("tx with empty inputs, rejected for tx id= " << get_transaction_hash(tx)); @@ -56,7 +56,7 @@ namespace currency if (!check_outs_valid(tx)) { - LOG_PRINT_RED_L0("tx with invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); + LOG_PRINT_RED_L0("tx has invalid outputs, rejected for tx id= " << get_transaction_hash(tx)); return false; } @@ -66,18 +66,6 @@ namespace currency return false; } - if (!check_tx_balance(tx)) - { - LOG_PRINT_RED_L0("balance check failed for tx " << get_transaction_hash(tx)); - return false; - } - - if (tx_block_size >= CURRENCY_MAX_TRANSACTION_BLOB_SIZE) - { - LOG_PRINT_RED_L0("tx has too big size " << tx_block_size << ", expected no bigger than " << CURRENCY_BLOCK_GRANTED_FULL_REWARD_ZONE); - return false; - } - //check if tx use different key images if (!check_tx_inputs_keyimages_diff(tx)) { @@ -91,6 +79,17 @@ namespace currency return false; } + // inexpensive check for pre-HF4 txs + // post-HF4 txs balance are being checked in check_tx_balance() + if (tx.version <= TRANSACTION_VERSION_PRE_HF4) + { + if (!check_tx_bare_balance(tx)) + { + LOG_PRINT_RED_L0("balance check failed for tx " << get_transaction_hash(tx)); + return false; + } + } + return true; } } \ No newline at end of file diff --git a/src/currency_core/tx_semantic_validation.h b/src/currency_core/tx_semantic_validation.h index 2e6f4790..03a946c7 100644 --- a/src/currency_core/tx_semantic_validation.h +++ b/src/currency_core/tx_semantic_validation.h @@ -1,16 +1,14 @@ -// Copyright (c) 2018-2019 Zano Project +// Copyright (c) 2018-2023 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 "include_base_utils.h" #include "currency_format_utils_transactions.h" namespace currency { - //check correct values, amounts and all lightweight checks not related with database + //check correct values, ins and outs types, amounts and all lightweight checks not related to the database bool validate_tx_semantic(const transaction& tx, size_t tx_block_size); } diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 42112077..37893382 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -363,10 +363,12 @@ namespace currency } res.status = API_RETURN_CODE_OK; + + /* std::stringstream ss; typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::outs_for_amount outs_for_amount; typedef COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::out_entry out_entry; - /*std::for_each(res.outs.begin(), res.outs.end(), [&](outs_for_amount& ofa) + std::for_each(res.outs.begin(), res.outs.end(), [&](outs_for_amount& ofa) { ss << "[" << ofa.amount << "]:"; CHECK_AND_ASSERT_MES(ofa.outs.size(), ;, "internal error: ofa.outs.size() is empty for amount " << ofa.amount); @@ -379,7 +381,7 @@ namespace currency std::string s = ss.str(); LOG_PRINT_L2("COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS: " << ENDL << s); */ - res.status = API_RETURN_CODE_OK; + return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -875,7 +877,7 @@ namespace currency blobdata block_blob = t_serializable_object_to_blob(resp.b); res.blocktemplate_blob = string_tools::buff_to_hex_nodelimer(block_blob); res.prev_hash = string_tools::pod_to_hex(resp.b.prev_id); - res.blinding_masks_sum = resp.blinding_mask_sum; + res.miner_tx_ogc = resp.miner_tx_ogc; res.height = resp.height; //calculate epoch seed res.seed = currency::ethash_epoch_to_seed(currency::ethash_height_to_epoch(res.height)); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index ce904da3..13f5fb49 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -84,7 +84,7 @@ namespace currency struct asset_id_kv { - crypto::hash asset_id; + crypto::public_key asset_id; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_POD_AS_HEX_STRING(asset_id) @@ -359,16 +359,17 @@ namespace currency struct out_entry { out_entry() = default; - out_entry(uint64_t global_amount_index, const crypto::public_key& stealth_address, const crypto::public_key& amount_commitment, const crypto::public_key& concealing_point) - : global_amount_index(global_amount_index), stealth_address(stealth_address), amount_commitment(amount_commitment), concealing_point(concealing_point) - {} out_entry(uint64_t global_amount_index, const crypto::public_key& stealth_address) - : global_amount_index(global_amount_index), stealth_address(stealth_address), amount_commitment{}, concealing_point{} + : global_amount_index(global_amount_index), stealth_address(stealth_address), concealing_point{}, amount_commitment{}, blinded_asset_id{} + {} + out_entry(uint64_t global_amount_index, const crypto::public_key& stealth_address, const crypto::public_key& amount_commitment, const crypto::public_key& concealing_point, const crypto::public_key& blinded_asset_id) + : global_amount_index(global_amount_index), stealth_address(stealth_address), concealing_point(concealing_point), amount_commitment(amount_commitment), blinded_asset_id(blinded_asset_id) {} uint64_t global_amount_index; crypto::public_key stealth_address; - crypto::public_key concealing_point; - crypto::public_key amount_commitment; + crypto::public_key concealing_point; // premultiplied by 1/8 + crypto::public_key amount_commitment; // premultiplied by 1/8 + crypto::public_key blinded_asset_id; // premultiplied by 1/8 }; #pragma pack(pop) @@ -859,7 +860,7 @@ namespace currency crypto::hash seed; blobdata blocktemplate_blob; std::string prev_hash; - crypto::scalar_t blinding_masks_sum; // sum of outputs' blinding masks (for zc outs) + outputs_generation_context miner_tx_ogc; std::string status; BEGIN_KV_SERIALIZE_MAP() @@ -868,7 +869,7 @@ namespace currency KV_SERIALIZE_POD_AS_HEX_STRING(seed) KV_SERIALIZE(blocktemplate_blob) KV_SERIALIZE(prev_hash) - KV_SERIALIZE(blinding_masks_sum) + KV_SERIALIZE(miner_tx_ogc) KV_SERIALIZE(status) END_KV_SERIALIZE_MAP() }; diff --git a/src/serialization/boost_types.h b/src/serialization/boost_types.h new file mode 100644 index 00000000..154705b8 --- /dev/null +++ b/src/serialization/boost_types.h @@ -0,0 +1,60 @@ +// 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 +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#pragma once + +#include + + +template