diff --git a/src/crypto/crypto-sugar.h b/src/crypto/crypto-sugar.h index 8a3141d3..59503eff 100644 --- a/src/crypto/crypto-sugar.h +++ b/src/crypto/crypto-sugar.h @@ -1144,6 +1144,14 @@ namespace crypto return hs_calculator.calc_hash(); } + static scalar_t hs(const char(&str32)[32], const crypto::point_t& p) + { + hs_t hs_calculator(2); + hs_calculator.add_32_chars(str32); + hs_calculator.add_point(p); + return hs_calculator.calc_hash(); + } + static point_t hp(const point_t& p) { point_t result; diff --git a/src/currency_core/crypto_config.h b/src/currency_core/crypto_config.h index ec293cba..06eaa3e8 100644 --- a/src/currency_core/crypto_config.h +++ b/src/currency_core/crypto_config.h @@ -21,3 +21,4 @@ #define CRYPTO_HDS_CLSAG_GGXG_CHALLENGE "ZANO_HDS_CLSAG_GGXG_CHALLENGE__" #define CRYPTO_HDS_ZARCANUM_LAST_POW_HASH "ZANO_HDS_ZARCANUM_LAST_POW_HASH" +#define CRYPTO_HDS_ZARCANUM_SECRET_Q "ZANO_HDS_ZARCANUM_SECRET_Q_____" diff --git a/src/currency_core/currency_format_utils.cpp b/src/currency_core/currency_format_utils.cpp index d31e04b4..7671652a 100644 --- a/src/currency_core/currency_format_utils.cpp +++ b/src/currency_core/currency_format_utils.cpp @@ -270,27 +270,31 @@ 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()); - + // input #0: txin_gen txin_gen in; in.height = height; tx.vin.push_back(in); if (pos) { - // TODO: add Zarcanum part - - txin_to_key posin; - posin.amount = pe.amount; - - // TODO: using pe.index is deprecated, get input's global index by pe.tx_id and pe.tx_out_index - posin.key_offsets.push_back(pe.g_index); - - posin.k_image = pe.keyimage; - tx.vin.push_back(posin); - //reserve place for ring signature - tx.signatures.resize(1); - tx.signatures[0] = NLSAG_sig(); - boost::get(tx.signatures[0]).s.resize(posin.key_offsets.size()); + // input #1: stake (for PoS blocks only) + if (tx.version > TRANSACTION_VERSION_PRE_HF4) + { + // TODO: add Zarcanum part + } + else + { + // old fashioned tx non-hidden amounts PoS scheme + txin_to_key stake_input; + stake_input.amount = pe.amount; + stake_input.key_offsets.push_back(pe.g_index); + stake_input.k_image = pe.keyimage; + tx.vin.push_back(stake_input); + //reserve place for ring signature + NLSAG_sig nlsag; + nlsag.s.resize(stake_input.key_offsets.size()); + tx.signatures.push_back(nlsag); // consider using emplace_back and avoid copying + } } uint64_t no = 0; @@ -574,10 +578,10 @@ namespace currency std::string print_stake_kernel_info(const stake_kernel& sk) { std::stringstream ss; - ss << "block_timestamp: " << sk.block_timestamp << ENDL - << "kimage: " << sk.kimage << ENDL - << "stake_modifier.last_pos_kernel_id: " << sk.stake_modifier.last_pos_kernel_id << ENDL - << "stake_modifier.last_pow_id: " << sk.stake_modifier.last_pow_id << ENDL; + ss << "block timestamp: " << sk.block_timestamp << ENDL + << "key image: " << sk.kimage << ENDL + << "sm.last_pos_kernel_id: " << sk.stake_modifier.last_pos_kernel_id << ENDL + << "sm.last_pow_id: " << sk.stake_modifier.last_pow_id << ENDL; return ss.str(); } @@ -2430,7 +2434,7 @@ namespace currency return true; } - bool is_out_to_acc(const account_keys& acc, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount) + bool is_out_to_acc(const account_keys& acc, const tx_out_zarcanum& zo, const crypto::key_derivation& derivation, size_t output_index, uint64_t& decoded_amount, crypto::scalar_t& blinding_mask) { crypto::scalar_t h = crypto::hash_helper_t::hs(reinterpret_cast(derivation), output_index); // h = Hs(8 * r * V, i) @@ -2445,7 +2449,7 @@ 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]; - crypto::scalar_t blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_BLINDING_MASK, h); // f = Hs(domain_sep, h) + blinding_mask = crypto::hash_helper_t::hs(CRYPTO_HDS_OUT_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 @@ -2569,11 +2573,12 @@ namespace currency VARIANT_SWITCH_END(); } VARIANT_CASE_CONST(tx_out_zarcanum, zo) - //@#@ - wallet_out_info woi(output_index, 0); - if (is_out_to_acc(acc, zo, derivation, output_index, woi.amount)) + uint64_t amount = 0; + crypto::scalar_t blinding_mask = 0; + if (is_out_to_acc(acc, zo, derivation, output_index, amount, blinding_mask)) { - outs.emplace_back(woi); + outs.emplace_back(output_index, amount, blinding_mask); + money_transfered += amount; } VARIANT_SWITCH_END(); output_index++; diff --git a/src/currency_core/currency_format_utils.h b/src/currency_core/currency_format_utils.h index 1f9a967b..c020c2dd 100644 --- a/src/currency_core/currency_format_utils.h +++ b/src/currency_core/currency_format_utils.h @@ -202,10 +202,15 @@ namespace currency : index(index) , amount(amount) {} + wallet_out_info(size_t index, uint64_t amount, const crypto::scalar_t& blinding_mask) + : index(index) + , amount(amount) + , blinding_mask(blinding_mask) + {} size_t index = SIZE_MAX; uint64_t amount = 0; - //todo: additional input info + crypto::scalar_t blinding_mask = 0; }; diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index 910f255e..aef50521 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -350,6 +350,12 @@ bool out_is_to_htlc(const currency::tx_out_v& out_t) } return false; } + +bool out_is_zc(const currency::tx_out_v& out_t) +{ + return out_t.type() == typeid(currency::tx_out_zarcanum); +} + const currency::txout_htlc& out_get_htlc(const currency::tx_out_v& out_t) { return boost::get(boost::get(out_t).target); @@ -380,11 +386,6 @@ uint8_t wallet2::out_get_mixin_attr(const currency::tx_out_v& out_t) return false; } -bool out_is_to_zarcanum(const currency::tx_out_v& out_t) -{ - return out_t.type() == typeid(currency::tx_out_zarcanum); -} - const crypto::public_key& wallet2::out_get_pub_key(const currency::tx_out_v& out_t, std::list& htlc_info_list) { if (out_t.type() == typeid(tx_out_bare)) @@ -541,12 +542,15 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t for (size_t i_in_outs = 0; i_in_outs != outs.size(); i_in_outs++) { - size_t o = outs[i_in_outs].index; + const wallet_out_info& out = outs[i_in_outs]; + size_t o = out.index; WLT_THROW_IF_FALSE_WALLET_INT_ERR_EX(o < tx.vout.size(), "wrong out in transaction: internal index=" << o << ", total_outs=" << tx.vout.size()); { const currency::tx_out_v& out_v = tx.vout[o]; - if (out_is_to_key(out_v) || out_is_to_htlc(out_v) || out_is_to_zarcanum(out_v)) // out.target.type() == typeid(txout_to_key) || out.target.type() == typeid(txout_htlc)) + bool out_type_zc = out_is_zc(out_v); + bool out_type_to_key = out_is_to_key(out_v); + if (out_type_zc || out_type_to_key || out_is_to_htlc(out_v)) { crypto::public_key out_key = out_get_pub_key(out_v, htlc_info_list); //const currency::txout_to_key& otk = boost::get(out.target); @@ -601,7 +605,7 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t } } - if (is_auditable() && (out_is_to_key(out_v) || out_is_to_zarcanum(out_v)) && + if (is_auditable() && (out_type_to_key || out_type_zc) && out_get_mixin_attr(out_v) != CURRENCY_TO_KEY_OUT_FORCED_NO_MIX) { std::stringstream ss; @@ -643,6 +647,10 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t td.m_flags |= WALLET_TRANSFER_DETAIL_FLAG_MINED_TRANSFER; } } + + if (out_type_zc) + td.m_opt_blinding_mask = out.blinding_mask; + size_t transfer_index = m_transfers.size() - 1; if (out_is_to_htlc(out_v)) { @@ -699,7 +707,7 @@ void wallet2::process_new_transaction(const currency::transaction& tx, uint64_t if (max_out_unlock_time < get_tx_unlock_time(tx, o)) max_out_unlock_time = get_tx_unlock_time(tx, o); - if (out_is_to_key(out_v) || out_is_to_zarcanum(out_v)) + if (out_type_to_key || out_type_zc) { WLT_LOG_L0("Received money, transfer #" << transfer_index << ", amount: " << print_money(td.amount()) << ", with tx: " << get_transaction_hash(tx) << ", at height " << height); } @@ -3613,9 +3621,6 @@ bool wallet2::prepare_and_sign_pos_block(currency::block& b, //------------------------------------------------------------------ bool wallet2::fill_mining_context(mining_context& ctx) { - //bool r = get_pos_entries(ctx.sp.pos_entries); // TODO: Remove this call. Transfers are filtered in scan_pos - //WLT_CHECK_AND_ASSERT_MES(r, false, "Failed to get_pos_entries()"); - currency::COMMAND_RPC_GET_POS_MINING_DETAILS::request pos_details_req = AUTO_VAL_INIT(pos_details_req); currency::COMMAND_RPC_GET_POS_MINING_DETAILS::response pos_details_resp = AUTO_VAL_INIT(pos_details_resp); ctx.status = API_RETURN_CODE_NOT_FOUND; @@ -3623,13 +3628,15 @@ bool wallet2::fill_mining_context(mining_context& ctx) if (pos_details_resp.status != API_RETURN_CODE_OK) return false; ctx.basic_diff.assign(pos_details_resp.pos_basic_difficulty); - ctx.sm = pos_details_resp.sm; + ctx.sk = AUTO_VAL_INIT(ctx.sk); + ctx.sk.stake_modifier = pos_details_resp.sm; - if (get_core_runtime_config().hard_forks.is_hardfork_active_for_height(4, get_top_block_height() + 1)) + if (is_in_hardfork_zone(ZANO_HARDFORK_04_ZARCANUM)) { // Zarcanum (PoS with hidden amounts) ctx.zarcanum = true; - ctx.last_pow_block_id_hashed = crypto::hash_helper_t::hs(CRYPTO_HDS_ZARCANUM_LAST_POW_HASH, ctx.sm.last_pow_id); + ctx.last_pow_block_id_hashed = crypto::hash_helper_t::hs(CRYPTO_HDS_ZARCANUM_LAST_POW_HASH, ctx.sk.stake_modifier.last_pow_id); + ctx.l_div_z_D = crypto::c_scalar_L.as_boost_mp_type() / (ctx.basic_diff); } ctx.last_block_hash = pos_details_resp.last_block_hash; @@ -3655,10 +3662,6 @@ bool wallet2::try_mint_pos(const currency::account_public_address& miner_address return true; } - //uint64_t pos_entries_amount = 0; - //for (auto& ent : ctx.sp.pos_entries) - // pos_entries_amount += ent.amount; - std::atomic stop(false); scan_pos(ctx, stop, [this](){ size_t blocks_fetched; @@ -3681,61 +3684,76 @@ bool wallet2::try_mint_pos(const currency::account_public_address& miner_address return true; } //------------------------------------------------------------------ -void wallet2::do_pos_mining_prepare_entry(mining_context& cxt, size_t transfer_index) +void wallet2::do_pos_mining_prepare_entry(mining_context& context, size_t transfer_index) { - if (cxt.zarcanum) - { - //uint64_t pos_entry_wallet_index = cxt.sp.pos_entries[pos_entry_index].wallet_index; - //CHECK_AND_ASSERT_MES_NO_RET(pos_entry_wallet_index < m_transfers.size(), "invalid pos_entry_wallet_index = " << pos_entry_wallet_index << ", m_transfers: " << m_transfers.size()); + CHECK_AND_ASSERT_MES_NO_RET(transfer_index < m_transfers.size(), "transfer_index is out of bounds: " << transfer_index); + const transfer_details& td = m_transfers[transfer_index]; + // pre build kernel + context.sk.kimage = td.m_key_image; + + if (context.zarcanum) + { + crypto::point_t R(get_tx_pub_key_from_extra(td.m_ptx_wallet_info->m_tx)); + crypto::scalar_t v = m_account.get_keys().view_secret_key; + context.secret_q = v * crypto::hash_helper_t::hs(CRYPTO_HDS_ZARCANUM_SECRET_Q, v * R); } } //------------------------------------------------------------------ -bool wallet2::do_pos_mining_iteration(mining_context& cxt, size_t transfer_index, uint64_t ts) +bool wallet2::do_pos_mining_iteration(mining_context& context, size_t transfer_index, uint64_t ts) { CHECK_AND_NO_ASSERT_MES(transfer_index < m_transfers.size(), false, "transfer_index is out of bounds: " << transfer_index); const transfer_details& td = m_transfers[transfer_index]; - // build kernel - currency::stake_kernel sk = AUTO_VAL_INIT(sk); - sk.kimage = td.m_key_image; - sk.stake_modifier = cxt.sm; - sk.block_timestamp = ts; - - // calculate kernel hash + // update stake kernel and calculate it's hash + context.sk.block_timestamp = ts; crypto::hash kernel_hash; { PROFILE_FUNC("calc_hash"); - kernel_hash = crypto::cn_fast_hash(&sk, sizeof(sk)); + kernel_hash = crypto::cn_fast_hash(&context.sk, sizeof(context.sk)); } const uint64_t stake_amount = td.amount(); bool found = false; - if (cxt.zarcanum) + if (context.zarcanum && td.is_zc()) { - // TODO @#@# + PROFILE_FUNC("check_zarcanum"); + crypto::scalar_t lhs_s = crypto::scalar_t(kernel_hash) * (*td.m_opt_blinding_mask + context.secret_q + context.last_pow_block_id_hashed); // == h * (f + q + f') mod l + boost::multiprecision::uint256_t lhs = lhs_s.as_boost_mp_type(); + boost::multiprecision::uint256_t rhs = context.l_div_z_D * stake_amount; // == floor( l / (z * D) ) * a + + if (lhs < rhs) + { + found = true; + LOG_PRINT_GREEN("Found Zarcanum kernel: amount: " << currency::print_money_brief(stake_amount) << ", gindex: " << td.m_global_output_index << ENDL + << "difficulty: " << context.basic_diff << ENDL + << "kernel info: " << ENDL + << print_stake_kernel_info(context.sk) << ENDL + << "kernel_hash: " << kernel_hash << ENDL + << "lhs: " << lhs << ENDL + << "rhs: " << rhs + , LOG_LEVEL_0); + + } + ++context.iterations_processed; } else { // old PoS with non-hidden amounts - currency::wide_difficulty_type final_diff = cxt.basic_diff / stake_amount; + currency::wide_difficulty_type final_diff = context.basic_diff / stake_amount; { PROFILE_FUNC("check_hash"); found = currency::check_hash(kernel_hash, final_diff); - ++cxt.iterations_processed; + ++context.iterations_processed; } if (found) { - //cxt.index = pos_entry_index; - cxt.block_timestamp = ts; - - LOG_PRINT_GREEN("Found kernel: amount: " << currency::print_money_brief(stake_amount) << ENDL - << "difficulty: " << cxt.basic_diff << ", final_diff: " << final_diff << ENDL - << "index: " << td.m_global_output_index << ENDL - << "kernel info: " << ENDL - << print_stake_kernel_info(sk) << ENDL - << "kernel_hash(proof): " << kernel_hash, + LOG_PRINT_GREEN("Found kernel: amount: " << currency::print_money_brief(stake_amount)<< ", gindex: " << td.m_global_output_index << ENDL + << "difficulty: " << context.basic_diff << ", final_diff: " << final_diff << ENDL + << "kernel info: " << ENDL + << print_stake_kernel_info(context.sk) << ENDL + << "kernel_hash(proof): " << kernel_hash, LOG_LEVEL_0); } } @@ -3828,8 +3846,8 @@ bool wallet2::build_minted_block(const mining_context& cxt, const currency::txout_to_key& txtokey = boost::get(target); keys_ptrs.push_back(&txtokey.key); - // set a real timestamp - b.timestamp = cxt.block_timestamp; + // set the timestamp from stake kernel + b.timestamp = cxt.sk.block_timestamp; uint64_t current_timestamp = m_core_runtime_config.get_core_time(); set_block_datetime(current_timestamp, b); WLT_LOG_MAGENTA("Applying actual timestamp: " << current_timestamp, LOG_LEVEL_0); diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index c6ea137c..3ff5db39 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -381,6 +381,7 @@ namespace tools uint64_t m_spent_height; uint32_t m_flags; uint64_t m_amount; + boost::optional m_opt_blinding_mask; // @#@ will throw if type is not tx_out_bare, TODO: change according to new model, // need to replace all get_tx_out_bare_from_out_v() to proper code @@ -391,6 +392,7 @@ namespace tools bool is_spent() const { return m_flags & WALLET_TRANSFER_DETAIL_FLAG_SPENT; } bool is_spendable() const { return (m_flags & (WALLET_TRANSFER_DETAIL_FLAG_SPENT | WALLET_TRANSFER_DETAIL_FLAG_BLOCKED | WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION | WALLET_TRANSFER_DETAIL_FLAG_COLD_SIG_RESERVATION)) == 0; } bool is_reserved_for_escrow() const { return ( (m_flags & WALLET_TRANSFER_DETAIL_FLAG_ESCROW_PROPOSAL_RESERVATION) != 0 ); } + bool is_zc() const { return m_opt_blinding_mask != boost::none; } BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE_CUSTOM(m_ptx_wallet_info, const transaction_wallet_info&, tools::wallet2::transform_ptr_to_value, tools::wallet2::transform_value_to_ptr) @@ -398,6 +400,7 @@ namespace tools KV_SERIALIZE(m_spent_height) KV_SERIALIZE(m_flags) KV_SERIALIZE(m_amount) + //KV_SERIALIZE_N(m_opt_blinding_mask, blinding_mask) KV_SERIALIZE_EPHEMERAL_N(uint64_t, tools::wallet2::transfer_details_base_to_amount, "amount") KV_SERIALIZE_EPHEMERAL_N(std::string, tools::wallet2::transfer_details_base_to_tx_hash, "tx_id") END_KV_SERIALIZE_MAP() @@ -453,14 +456,18 @@ namespace tools uint64_t index; // index in m_transfers uint64_t stake_unlock_time; - uint64_t block_timestamp; + //uint64_t block_timestamp; uint64_t height; uint64_t starter_timestamp; crypto::hash last_block_hash; - crypto::scalar_t last_pow_block_id_hashed; // Zarcanum notation: f' + + crypto::scalar_t last_pow_block_id_hashed; // Zarcanum notation: f' + crypto::scalar_t secret_q; // Zarcanum notation: q + boost::multiprecision::uint256_t l_div_z_D; // Zarcanum notation: floor( l / (z * D) ) (max possible value: 2^252 / (2^64 * 1) ~= 2^188, or 2^252 / (1 * 1) = 2^252) currency::wide_difficulty_type basic_diff; - currency::stake_modifier_type sm; + currency::stake_kernel sk; + //currency::stake_modifier_type sm; uint64_t iterations_processed = 0; uint64_t total_items_checked = 0;