diff --git a/src/crypto/crypto-ops.c b/src/crypto/crypto-ops.c index 29822831..dad60907 100644 --- a/src/crypto/crypto-ops.c +++ b/src/crypto/crypto-ops.c @@ -30,6 +30,7 @@ #include #include "warnings.h" #include "crypto-ops.h" +#include "hash-ops.h" // for cn_fast_hash DISABLE_VS_WARNINGS(4146 4244) @@ -3724,3 +3725,38 @@ void sc_invert(unsigned char* out, const unsigned char* z) sc_mul(out, out, out); sc_mul(out, out, z); } + +/* + In: t (x, y, z) + Out: r (x, t, z, t) + + Note: expensive conversion because of fe_invert +*/ +void ge_p2_to_p3(ge_p3 *r, const ge_p2 *t) +{ + fe_copy(r->X, t->X); + fe_copy(r->Y, t->Y); + fe_copy(r->Z, t->Z); + fe_invert(r->T, t->Z); + fe_mul(r->T, r->T, t->Y); + fe_mul(r->T, r->T, t->X); +} + + +/* + In: ge_bytes -- points to 32 bytes of data + Out: res = Hp(ge_bytes) + where Hp = 8 * ge_fromfe_frombytes_vartime(cn_fast_hash(ge_bytes)) +*/ +void ge_bytes_hash_to_ec(ge_p3 *res, const unsigned char *ge_bytes) +{ + unsigned char h[HASH_SIZE]; + ge_p2 point; + ge_p1p1 point2; + + cn_fast_hash(ge_bytes, 32, h); + ge_fromfe_frombytes_vartime(&point, &h[0]); + /*ge_p2_to_p3(res, &point); /* -- can be used to avoid multiplication by 8 for debugging */ + ge_mul8(&point2, &point); + ge_p1p1_to_p3(res, &point2); +} diff --git a/src/crypto/crypto-ops.h b/src/crypto/crypto-ops.h index f94d5f86..07122772 100644 --- a/src/crypto/crypto-ops.h +++ b/src/crypto/crypto-ops.h @@ -105,6 +105,8 @@ void ge_scalarmult_p3(ge_p3 *, const unsigned char *, const ge_p3 *); void ge_double_scalarmult_precomp_vartime(ge_p2 *, const unsigned char *, const ge_p3 *, const unsigned char *, const ge_dsmp); void ge_mul8(ge_p1p1 *, const ge_p2 *); void ge_fromfe_frombytes_vartime(ge_p2 *, const unsigned char *); +void ge_bytes_hash_to_ec(ge_p3 *, const unsigned char *); + void ge_p3_0(ge_p3 *h); void ge_sub(ge_p1p1 *, const ge_p3 *, const ge_cached *); diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index ec93366d..8c51959f 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -303,11 +303,13 @@ namespace crypto { PUSH_VS_WARNINGS DISABLE_VS_WARNINGS(4200) -struct rs_comm_entry -{ - ec_point a, b; -}; - struct rs_comm { + struct rs_comm_entry + { + ec_point a, b; + }; + + struct rs_comm + { hash h; struct rs_comm_entry ab[]; }; @@ -422,4 +424,5 @@ POP_VS_WARNINGS sc_sub(&h, &h, &sum); return sc_isnonzero(&h) == 0; } -} + +} // namespace crypto diff --git a/src/currency_core/blockchain_storage.cpp b/src/currency_core/blockchain_storage.cpp index 7101e714..a98d2799 100644 --- a/src/currency_core/blockchain_storage.cpp +++ b/src/currency_core/blockchain_storage.cpp @@ -6167,9 +6167,8 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, if (abg_it != alt_chain.back()->second.gindex_lookup_table.end()) { amount_touched_altchain = true; - //Notice: since transactions is not allowed to refer to each other in one block, then we can consider that index in - //tx input would be always less then top for previous block, so just take it - global_outs_for_amount = abg_it->second; + // local gindex lookup table contains last used gindex, so we can't get total number of outs + // just skip setting global_outs_for_amount } else { @@ -6194,7 +6193,7 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, if (off.type() == typeid(uint64_t)) { uint64_t offset_gindex = boost::get(off); - CHECK_AND_ASSERT_MES(offset_gindex < global_outs_for_amount, false, + CHECK_AND_ASSERT_MES(amount_touched_altchain || (offset_gindex < global_outs_for_amount), false, "invalid global output index " << offset_gindex << " for amount=" << input_to_key.amount << ", max is " << global_outs_for_amount << ", referred to by offset #" << pk_n << @@ -6226,7 +6225,7 @@ bool blockchain_storage::validate_alt_block_input(const transaction& input_tx, } } if (found_the_key) - break; + continue; //otherwise lookup in main chain index } auto p = m_db_outputs.get_subitem(input_to_key.amount, offset_gindex); diff --git a/tests/core_tests/chain_switch_1.cpp b/tests/core_tests/chain_switch_1.cpp index 2657ba5c..0bb66a66 100644 --- a/tests/core_tests/chain_switch_1.cpp +++ b/tests/core_tests/chain_switch_1.cpp @@ -622,3 +622,135 @@ bool alt_blocks_with_the_same_txs::check_tx_not_related_to_altblock(currency::co return true; } +//----------------------------------------------------------------------------------------------------- + +bool chain_switching_when_out_spent_in_alt_chain_mixin::generate(std::vector& events) const +{ + // Test idea: make sure a tx can spend an output with mixins from another tx when both txs are in an altchain. + bool r = false; + GENERATE_ACCOUNT(miner_acc); + GENERATE_ACCOUNT(alice_acc); + GENERATE_ACCOUNT(bob_acc); + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc); + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + // 0 1 11 12 13 14 + // (0 )- (1 )-...(1r)- (2 )- (3 )- <-- main chain + // \ + // \ + // \- (2a)- (3a)- (4a)- + // tx_0 <- tx_1 // tx_1 spends output from tx_0 + + // send batch of 10 x 5 test coins to Alice for easier tx_1 construction (and to generate free decoys) + transaction tx_0; + r = construct_tx_with_many_outputs(events, blk_1r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(50), 10, + TESTS_DEFAULT_FEE, tx_0); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed"); + events.push_back(tx_0); + + MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc); + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); + + MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0); + + // make sure Alice received exactly 50 test coins + CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0); + REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_2a, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); + CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(50)); + + // Alice spends her 5 test coins received by tx_0 + MAKE_TX_FEE_MIX(events, tx_1, alice_acc, bob_acc, MK_TEST_COINS(4), TESTS_DEFAULT_FEE, 3 /* nmix */, blk_2a); + events.pop_back(); // pop back tx_1 as it won't go into the tx pool normally because of alt chain + + // simulate handling a block with that tx: handle tx like going with the block... + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); + events.push_back(tx_1); + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); + + MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_1); + MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc); + + // make sure Alice has correct balance + REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, 2); + CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(45)); + + // make sure chain successfully switched + DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a))); + + return true; +} + +//----------------------------------------------------------------------------------------------------- + +bool chain_switching_when_out_spent_in_alt_chain_ref_id::generate(std::vector& events) const +{ + // Test idea: make sure tx can spend (using ref_by_id) an output from another tx when both txs are in an altchain. + bool r = false; + GENERATE_ACCOUNT(miner_acc); + GENERATE_ACCOUNT(alice_acc); + GENERATE_ACCOUNT(bob_acc); + MAKE_GENESIS_BLOCK(events, blk_0, miner_acc, test_core_time::get_time()); + MAKE_NEXT_BLOCK(events, blk_1, blk_0, miner_acc); + REWIND_BLOCKS_N(events, blk_1r, blk_1, miner_acc, CURRENCY_MINED_MONEY_UNLOCK_WINDOW); + + // 0 1 11 12 13 14 + // (0 )- (1 )-...(1r)- (2 )- (3 )- <- main chain + // \ + // \ + // \- (2a)- (3a)- (4a)- + // tx_0 <- tx_1 // tx_1 spends an output from tx_0 using ref_by_id and mixins + + // send batch of 10 x 5 test coins to Alice for easier tx_0 construction + transaction tx_0; + r = construct_tx_with_many_outputs(events, blk_1r, miner_acc.get_keys(), alice_acc.get_public_address(), MK_TEST_COINS(50), 10, + TESTS_DEFAULT_FEE, tx_0, true); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_with_many_outputs failed"); + + // make sure tx_0 really use ref_by_id + size_t refs_count = 0, gindex_count = 0; + count_ref_by_id_and_gindex_refs_for_tx_inputs(tx_0, refs_count, gindex_count); + CHECK_AND_ASSERT_MES(refs_count == 1 && gindex_count == 0, false, "incorrect input references: " << refs_count << ", " << gindex_count); + + events.push_back(tx_0); + + MAKE_NEXT_BLOCK(events, blk_2, blk_1r, miner_acc); + MAKE_NEXT_BLOCK(events, blk_3, blk_2, miner_acc); + + MAKE_NEXT_BLOCK_TX1(events, blk_2a, blk_1r, miner_acc, tx_0); + + // make sure Alice received exactly 50 test coins + CREATE_TEST_WALLET(alice_wlt, alice_acc, blk_0); + REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_2a, CURRENCY_MINED_MONEY_UNLOCK_WINDOW + 2); + CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(50)); + + // Alice spends her 5 test coins received by tx_0 + transaction tx_1; + std::vector destinations; + destinations.push_back(tx_destination_entry(MK_TEST_COINS(4), bob_acc.get_public_address())); + size_t nmix = 3; + r = construct_tx_to_key(events, tx_1, blk_2a, alice_acc, destinations, TESTS_DEFAULT_FEE, nmix, 0, empty_extra, empty_attachment, true, true, true); + CHECK_AND_ASSERT_MES(r, false, "construct_tx_to_key failed"); + + // make sure tx_1 really use ref_by_id + refs_count = 0, gindex_count = 0; + count_ref_by_id_and_gindex_refs_for_tx_inputs(tx_1, refs_count, gindex_count); + CHECK_AND_ASSERT_MES(refs_count == nmix + 1 && gindex_count == 0, false, "incorrect input references: " << refs_count << ", " << gindex_count); + + // simulate handling a block with that tx: handle tx like going with the block... + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, true)); + events.push_back(tx_1); + events.push_back(event_visitor_settings(event_visitor_settings::set_txs_kept_by_block, false)); + + MAKE_NEXT_BLOCK_TX1(events, blk_3a, blk_2a, miner_acc, tx_1); + MAKE_NEXT_BLOCK(events, blk_4a, blk_3a, miner_acc); + + // make sure Alice has correct balance + REFRESH_TEST_WALLET_AT_GEN_TIME(events, alice_wlt, blk_4a, 2); + CHECK_TEST_WALLET_BALANCE_AT_GEN_TIME(alice_wlt, MK_TEST_COINS(45)); + + // make sure chain successfully switched + DO_CALLBACK_PARAMS(events, "check_top_block", params_top_block(get_block_height(blk_4a), get_block_hash(blk_4a))); + + return true; +} diff --git a/tests/core_tests/chain_switch_1.h b/tests/core_tests/chain_switch_1.h index 0605a5d2..f19e99b9 100644 --- a/tests/core_tests/chain_switch_1.h +++ b/tests/core_tests/chain_switch_1.h @@ -72,3 +72,13 @@ struct alt_blocks_with_the_same_txs : public test_chain_unit_enchanced bool check_tx_related_to_altblock(currency::core& c, size_t ev_index, const std::vector& events); bool check_tx_not_related_to_altblock(currency::core& c, size_t ev_index, const std::vector& events); }; + +struct chain_switching_when_out_spent_in_alt_chain_mixin : public test_chain_unit_enchanced +{ + bool generate(std::vector& events) const; +}; + +struct chain_switching_when_out_spent_in_alt_chain_ref_id : public test_chain_unit_enchanced +{ + bool generate(std::vector& events) const; +}; diff --git a/tests/core_tests/chaingen.cpp b/tests/core_tests/chaingen.cpp index 4fc551cd..3af55b49 100644 --- a/tests/core_tests/chaingen.cpp +++ b/tests/core_tests/chaingen.cpp @@ -1548,10 +1548,10 @@ transaction construct_tx_with_fee(std::vector& events, const b bool construct_tx_with_many_outputs(std::vector& events, const currency::block& blk_head, const currency::account_keys& keys_from, const currency::account_public_address& addr_to, - uint64_t total_amount, size_t outputs_count, uint64_t fee, currency::transaction& tx) + uint64_t total_amount, size_t outputs_count, uint64_t fee, currency::transaction& tx, bool use_ref_by_id /* = false */) { std::vector sources; - bool r = fill_tx_sources(sources, events, blk_head, keys_from, total_amount + fee, 0, true, false, false); + bool r = fill_tx_sources(sources, events, blk_head, keys_from, total_amount + fee, 0, true, false, use_ref_by_id); CHECK_AND_ASSERT_MES(r, false, "fill_tx_sources failed"); std::vector destinations; diff --git a/tests/core_tests/chaingen.h b/tests/core_tests/chaingen.h index daf36a83..c5731018 100644 --- a/tests/core_tests/chaingen.h +++ b/tests/core_tests/chaingen.h @@ -599,7 +599,7 @@ currency::transaction construct_tx_with_fee(std::vector& event bool construct_tx_with_many_outputs(std::vector& events, const currency::block& blk_head, const currency::account_keys& keys_from, const currency::account_public_address& addr_to, - uint64_t total_amount, size_t outputs_count, uint64_t fee, currency::transaction& tx); + uint64_t total_amount, size_t outputs_count, uint64_t fee, currency::transaction& tx, bool use_ref_by_id = false); void get_confirmed_txs(const std::vector& blockchain, const map_hash2tx_t& mtx, map_hash2tx_t& confirmed_txs); bool find_block_chain(const std::vector& events, std::vector& blockchain, map_hash2tx_t& mtx, const crypto::hash& head); @@ -916,6 +916,26 @@ inline uint64_t get_sources_total_amount(const std::vector(in); + for (auto& ko : in2key.key_offsets) + { + if (ko.type() == typeid(currency::ref_by_id)) + ++refs_by_id; + else if (ko.type() == typeid(uint64_t)) + ++refs_by_gindex; + } + } +} + template void append_vector_by_another_vector(U& dst, const V& src) { diff --git a/tests/core_tests/chaingen_main.cpp b/tests/core_tests/chaingen_main.cpp index 15a3fb43..c61c8986 100644 --- a/tests/core_tests/chaingen_main.cpp +++ b/tests/core_tests/chaingen_main.cpp @@ -899,6 +899,9 @@ int main(int argc, char* argv[]) GENERATE_AND_PLAY(gen_simple_chain_split_1); GENERATE_AND_PLAY(alt_blocks_validation_and_same_new_amount_in_two_txs); GENERATE_AND_PLAY(alt_blocks_with_the_same_txs); + GENERATE_AND_PLAY(chain_switching_when_out_spent_in_alt_chain_mixin); + // GENERATE_AND_PLAY(chain_switching_when_out_spent_in_alt_chain_ref_id); + // miscellaneous tests GENERATE_AND_PLAY(test_blockchain_vs_spent_keyimges); diff --git a/tests/functional_tests/L2S.h b/tests/functional_tests/L2S.h new file mode 100644 index 00000000..e60e139f --- /dev/null +++ b/tests/functional_tests/L2S.h @@ -0,0 +1,110 @@ +// Copyright (c) 2020 Zano Project (https://zano.org/) +// Copyright (c) 2020 Locksmith (acmxddk@gmail.com) +// Copyright (c) 2020 sowle (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 + +// +// This file contains the implementation of L2S membership proof protocol +// and a linkable ring signature scheme based on it. +// + +point_t ml2s_rsum_impl(size_t n, size_t N, std::vector::const_iterator X_array_bg_it, const std::vector& c1_array, + const std::vector& c3_array, const scalar_t& cn) +{ + if (n == 1) + return *X_array_bg_it + cn * *(X_array_bg_it + 1); + + // n >= 2, N >= 4 + return ml2s_rsum_impl(n - 1, N / 2, X_array_bg_it, c1_array, c3_array, c1_array[n - 2]) + + cn * ml2s_rsum_impl(n - 1, N / 2, X_array_bg_it + N / 2, c1_array, c3_array, c3_array[n - 2]); +} + +bool ml2s_rsum(size_t n, const std::vector& X_array, const std::vector& c1_array, + const std::vector& c3_array, point_t& result) +{ + size_t N = (size_t)1 << n; + CHECK_AND_ASSERT_MES(n != 0, false, "n == 0"); + CHECK_AND_ASSERT_MES(N == X_array.size(), false, "|X_array| != N, " << X_array.size() << ", " << N); + CHECK_AND_ASSERT_MES(c1_array.size() == n, false, "|c1_array| != n, " << c1_array.size() << ", " << n); + CHECK_AND_ASSERT_MES(c3_array.size() == n - 1, false, "|c3_array| != n - 1, " << c3_array.size() << ", " << n - 1); + + result = ml2s_rsum_impl(n, N, X_array.begin(), c1_array, c3_array, c1_array[n - 1]); + return true; +} + +struct ml2s_signature_element +{ + point_t Z0; + point_t T0; + scalar_t t0; + point_t Z; + std::vector r_array; + std::vector H_array; + point_t T; + scalar_t t; +}; + +struct ml2s_signature +{ + scalar_t z; + std::vector elements; +}; + +// reference: mL2SLnkSig_Verif() +bool ml2s_lnk_sig_verif(const scalar_t& m, const std::vector& B_array, const ml2s_signature& signature, uint8_t* p_err = nullptr) +{ +#define CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(cond, err_code) \ + if (!(cond)) { LOG_PRINT_RED("ml2s_lnk_sig_verif: \"" << #cond << "\" is false at " << LOCATION_SS << ENDL << "error code = " << err_code, LOG_LEVEL_3); \ + if (p_err) *p_err = err_code; return false; } + + auto hash_point_lambda = [&signature](const point_t& point) { return point + signature.z * hash_helper_t::hp(point); }; + + size_t L = signature.elements.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(L > 0, 0); + size_t n = signature.elements[0].r_array.size(); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(n < 32, 4); + size_t N = (size_t)1 << n; + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(B_array.size() == N / 2, 5); + + std::vector I_array(L); + std::vector A_array(L); + + for (size_t i = 0; i < L; ++i) + { + I_array[i] = (signature.elements[i].Z0 - c_point_G) / signature.z; + A_array[i] = signature.elements[i].Z0; + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(signature.elements[i].r_array.size() == n, 1); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(signature.elements[i].H_array.size() == n, 2); + } + + scalar_t z_ = hash_helper_t::hs(m, B_array, I_array); + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(z_ == signature.z, 3); + + scalar_t e = hash_helper_t::hs(signature.z); + + // ref: mL2SHPoM_Verif() + + // ref: X = mL2SHPoM_GetDecoySet(N, A, hash_point_cb, preimage_set_gen_cb) + std::vector P_array(B_array.size()); + for (size_t i = 0; i < B_array.size(); ++i) + P_array[i] = hash_point_lambda(B_array[i]); + + point_t Q_shift = hash_helper_t::hs(A_array, P_array) * c_point_G; + + CHECK_AND_FAIL_WITH_ERROR_IF_FALSE(P_array.size() * 2 == N, 6); + std::vector X_array(N); + // X_array = { P_array[0], Q_array[0], P_array[1], Q_array[1], etc. + for (size_t i = 0; i < N; ++i) + { + if (i % 2 == 0) + X_array[i] = P_array[i / 2]; + else + X_array[i] = hash_point_lambda(Q_shift + B_array[i / 2]); + } + + + return false; +#undef CHECK_AND_FAIL_WITH_ERROR_IF_FALSE +} diff --git a/tests/functional_tests/crypto_tests.cpp b/tests/functional_tests/crypto_tests.cpp index 0b04da05..4ba6717d 100644 --- a/tests/functional_tests/crypto_tests.cpp +++ b/tests/functional_tests/crypto_tests.cpp @@ -130,6 +130,7 @@ void sc_invert2(unsigned char* recip, const unsigned char* s) sc_sqmul(recip, 8, _11101011); } +extern void *sha3(const void *in, size_t inlen, void *md, int mdlen); // @@ -177,7 +178,6 @@ static const fe scalar_L_fe = { 16110573, 10012311, -6632702, 16062397, 5471207, __declspec(align(32)) struct scalar_t { - //fe m_fe; // 40 bytes, array 10 * 4, optimized form union { uint64_t m_u64[4]; @@ -189,6 +189,7 @@ struct scalar_t scalar_t() {} + // won't check scalar range validity (< L) scalar_t(uint64_t a0, uint64_t a1, uint64_t a2, uint64_t a3) { m_u64[0] = a0; @@ -197,14 +198,28 @@ struct scalar_t m_u64[3] = a3; } + // won't check secret key validity (sk < L) + scalar_t(const crypto::secret_key& sk) + { + from_secret_key(sk); + } + + // copy data and reduce + scalar_t(const crypto::hash& hash) + { + m_u64[0] = ((uint64_t*)&hash)[0]; + m_u64[1] = ((uint64_t*)&hash)[1]; + m_u64[2] = ((uint64_t*)&hash)[2]; + m_u64[3] = ((uint64_t*)&hash)[3]; + sc_reduce32(&m_s[0]); + } + scalar_t(uint64_t v) { zero(); if (v == 0) - { return; - } - reinterpret_cast(m_s) = v; + m_u64[0] = v; // do not need to call reduce as 2^64 < L } @@ -218,28 +233,49 @@ struct scalar_t return &m_s[0]; } + crypto::secret_key &as_secret_key() + { + return *(crypto::secret_key*)&m_s[0]; + } + + const crypto::secret_key& as_secret_key() const + { + return *(const crypto::secret_key*)&m_s[0]; + } + operator crypto::secret_key() const { crypto::secret_key result; memcpy(result.data, &m_s, sizeof result.data); - //fe_tobytes(reinterpret_cast(&result), m_fe); return result; } - bool from_secret_key(const crypto::secret_key& sk) + void from_secret_key(const crypto::secret_key& sk) { - //fe_frombytes(m_fe, reinterpret_cast(&sk)); - return false; + uint64_t *p_sk64 = (uint64_t*)&sk; + m_u64[0] = p_sk64[0]; + m_u64[1] = p_sk64[1]; + m_u64[2] = p_sk64[2]; + m_u64[3] = p_sk64[3]; + // assuming secret key is correct (< L), so we don't need to call reduce here } void zero() { - //fe_0(m_fe); m_u64[0] = 0; m_u64[1] = 0; m_u64[2] = 0; m_u64[3] = 0; - //memset(&m_s, 0, sizeof m_s); + } + + static scalar_t random() + { + unsigned char tmp[64]; + crypto::generate_random_bytes(64, tmp); + sc_reduce(tmp); + scalar_t result; + memcpy(&result.m_s, tmp, sizeof result.m_s); + return result; } void make_random() @@ -247,7 +283,7 @@ struct scalar_t unsigned char tmp[64]; crypto::generate_random_bytes(64, tmp); sc_reduce(tmp); - memcpy(&m_s, tmp, 32); + memcpy(&m_s, tmp, sizeof m_s); } bool is_zero() const @@ -453,6 +489,13 @@ struct point_t return true; }; + + friend std::ostream& operator<<(std::ostream& ss, const point_t &v) + { + crypto::public_key pk; + ge_p3_tobytes((unsigned char*)&pk, &v.m_p3); + return ss << epee::string_tools::pod_to_hex(pk); + } }; // struct point_t struct point_g_t : public point_t @@ -486,13 +529,106 @@ struct point_g_t : public point_t }; // struct point_g_t -static const point_g_t point_G; +static const point_g_t c_point_G; + +static const scalar_t c_scalar_L = { 0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; +static const scalar_t c_scalar_Lm1 = { 0x5812631a5cf5d3ec, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; +static const scalar_t c_scalar_P = { 0xffffffffffffffed, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; +static const scalar_t c_scalar_Pm1 = { 0xffffffffffffffec, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; +static const scalar_t c_scalar_256m1 = { 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff }; + + +// H_s hash function +struct hash_helper_t +{ + static scalar_t hs(const scalar_t& s) + { + scalar_t result = 0; + + crypto::cn_fast_hash(s.data(), sizeof s, (char*)result.data()); + + return result; + } + + static scalar_t hs(const scalar_t& s, const std::vector& ss, const std::vector& ps) + { + scalar_t result = 0; + return result; + } + + static scalar_t hs(const scalar_t& s, const std::vector& ps0, const std::vector& ps1) + { + scalar_t result = 0; + return result; + } + + static scalar_t hs(const std::vector& ps0, const std::vector& ps1) + { + scalar_t result = 0; + return result; + } + + static point_t hp(const point_t& p) + { + point_t result; + crypto::public_key pk = p; + + ge_bytes_hash_to_ec(&result.m_p3, (const unsigned char*)&pk); + + return result; + } +}; + +// +// test helpers +// + +inline std::ostream& operator<<(std::ostream& ss, const fe &f) +{ + constexpr size_t fe_index_max = (sizeof f / sizeof f[0]) - 1; + ss << "{"; + for (size_t i = 0; i <= fe_index_max; ++i) + ss << f[i] << ", "; + return ss << f[fe_index_max] << "}"; +} + +point_t point_from_str(const std::string& str) +{ + crypto::public_key pk; + if (!epee::string_tools::parse_tpod_from_hex_string(str, pk)) + throw std::runtime_error("couldn't parse pub key"); + + point_t result; + if (!result.from_public_key(pk)) + throw std::runtime_error("invalid pub key"); + + return result; +} + +scalar_t scalar_from_str(const std::string& str) +{ + crypto::secret_key sk; + if (!epee::string_tools::parse_tpod_from_hex_string(str, sk)) + throw std::runtime_error("couldn't parse sec key"); + + scalar_t result; + result.from_secret_key(sk); + if (result > c_scalar_Lm1) + throw std::runtime_error("sec key scalar >= L"); + + return result; +} + +//////////////////////////////////////////////////////////////////////////////// +#include "L2S.h" +//////////////////////////////////////////////////////////////////////////////// + + + +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// +//////////////////////////////////////////////////////////////////////////////// -static const scalar_t scalar_L = { 0x5812631a5cf5d3ed, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; -static const scalar_t scalar_Lm1 = { 0x5812631a5cf5d3ec, 0x14def9dea2f79cd6, 0x0, 0x1000000000000000 }; -static const scalar_t scalar_P = { 0xffffffffffffffed, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; -static const scalar_t scalar_Pm1 = { 0xffffffffffffffec, 0xffffffffffffffff, 0xffffffffffffffff, 0x7fffffffffffffff }; -static const scalar_t scalar_256m1 = { 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff }; /* @@ -676,31 +812,31 @@ TEST(crypto, scalar_basics) ASSERT_TRUE(z < z + 1); } - ASSERT_TRUE(scalar_L > 0 && !(scalar_L < 0)); - ASSERT_TRUE(scalar_Lm1 > 0 && !(scalar_Lm1 < 0)); - ASSERT_TRUE(scalar_Lm1 < scalar_L); - ASSERT_FALSE(scalar_Lm1 > scalar_L); - ASSERT_TRUE(scalar_P > scalar_Pm1); - ASSERT_FALSE(scalar_P < scalar_Pm1); + ASSERT_TRUE(c_scalar_L > 0 && !(c_scalar_L < 0)); + ASSERT_TRUE(c_scalar_Lm1 > 0 && !(c_scalar_Lm1 < 0)); + ASSERT_TRUE(c_scalar_Lm1 < c_scalar_L); + ASSERT_FALSE(c_scalar_Lm1 > c_scalar_L); + ASSERT_TRUE(c_scalar_P > c_scalar_Pm1); + ASSERT_FALSE(c_scalar_P < c_scalar_Pm1); std::cout << "0 = " << zero << std::endl; std::cout << "1 = " << one << std::endl; - std::cout << "L = " << scalar_L << std::endl; - std::cout << "L-1 = " << scalar_Lm1 << std::endl; - std::cout << "P = " << scalar_P << std::endl; - std::cout << "P-1 = " << scalar_Pm1 << std::endl; + std::cout << "L = " << c_scalar_L << std::endl; + std::cout << "L-1 = " << c_scalar_Lm1 << std::endl; + std::cout << "P = " << c_scalar_P << std::endl; + std::cout << "P-1 = " << c_scalar_Pm1 << std::endl; std::cout << std::endl; // check rolling over L for scalars arithmetics - ASSERT_EQ(scalar_Lm1 + 1, 0); - ASSERT_EQ(scalar_t(0) - 1, scalar_Lm1); - ASSERT_EQ(scalar_Lm1 * 2, scalar_Lm1 - 1); // (L - 1) * 2 = L + L - 2 = (L - 1) - 1 (mod L) - ASSERT_EQ(scalar_Lm1 * 100, scalar_Lm1 - 99); - ASSERT_EQ(scalar_Lm1 * scalar_Lm1, 1); // (L - 1) * (L - 1) = L*L - 2L + 1 = 1 (mod L) - ASSERT_EQ(scalar_Lm1 * (scalar_Lm1 - 1) * scalar_Lm1, scalar_Lm1 - 1); - ASSERT_EQ(scalar_L * scalar_L, 0); + ASSERT_EQ(c_scalar_Lm1 + 1, 0); + ASSERT_EQ(scalar_t(0) - 1, c_scalar_Lm1); + ASSERT_EQ(c_scalar_Lm1 * 2, c_scalar_Lm1 - 1); // (L - 1) * 2 = L + L - 2 = (L - 1) - 1 (mod L) + ASSERT_EQ(c_scalar_Lm1 * 100, c_scalar_Lm1 - 99); + ASSERT_EQ(c_scalar_Lm1 * c_scalar_Lm1, 1); // (L - 1) * (L - 1) = L*L - 2L + 1 = 1 (mod L) + ASSERT_EQ(c_scalar_Lm1 * (c_scalar_Lm1 - 1) * c_scalar_Lm1, c_scalar_Lm1 - 1); + ASSERT_EQ(c_scalar_L * c_scalar_L, 0); - ASSERT_EQ(scalar_t(3) / scalar_Lm1, scalar_t(3) * scalar_Lm1); // because (L - 1) ^ 2 = 1 + ASSERT_EQ(scalar_t(3) / c_scalar_Lm1, scalar_t(3) * c_scalar_Lm1); // because (L - 1) ^ 2 = 1 return true; } @@ -795,24 +931,24 @@ TEST(crypto, scalar_arithmetic_assignment) TEST(crypto, point_basics) { scalar_t s = 4; - point_t E = s * point_G; + point_t E = s * c_point_G; point_t X = 4 * E; - point_t K = 193847 * point_G; + point_t K = 193847 * c_point_G; point_t C = E + K; - ASSERT_EQ(X, 16 * point_G); + ASSERT_EQ(X, 16 * c_point_G); ASSERT_EQ(C - K, E); ASSERT_EQ(C - E, K); - ASSERT_EQ(C, (193847 + 4) * point_G); + ASSERT_EQ(C, (193847 + 4) * c_point_G); - ASSERT_EQ(point_G / 1, 1 * point_G); + ASSERT_EQ(c_point_G / 1, 1 * c_point_G); ASSERT_EQ(C / 3, E / 3 + K / 3); //ASSERT_EQ(K, 61 * (K / (61))); //ASSERT_EQ(K, 192847 * (K / scalar_t(192847))); ASSERT_EQ(K, 61 * (283 * (192847 * (K / (192847ull * 283 * 61))))); - ASSERT_EQ(E, point_G + point_G + point_G + point_G); - ASSERT_EQ(E - point_G, 3 * point_G); + ASSERT_EQ(E, c_point_G + c_point_G + c_point_G + c_point_G); + ASSERT_EQ(E - c_point_G, 3 * c_point_G); return true; } @@ -851,6 +987,59 @@ TEST(crypto, scalars) return true; } +// +// ML2S tests +// + +TEST(ml2s, rsum) +{ + // Ref: Rsum(3, 8, [1, 2, 3, 4, 5, 6, 7, 8], { 1: 1, 2 : 2, 3 : 3 }, { 1: 4, 2 : 5 }) == 659 + + point_t A = scalar_t::random() * c_point_G; + point_t result; + + bool r = ml2s_rsum(3, std::vector{ A, 2 * A, 3 * A, 4 * A, 5 * A, 6 * A, 7 * A, 8 * A }, + std::vector{ 1, 2, 3 }, std::vector{ 4, 5 }, result); + ASSERT_TRUE(r); + ASSERT_EQ(result, 659 * A); + + return true; +} + +TEST(ml2s, hs) +{ + scalar_t x = 2, p = 250; + //sc_exp(r.data(), x.data(), p.data()); + + x = 0; + + crypto::hash h; + scalar_t r; + + sha3(0, 0, &h, sizeof h); + LOG_PRINT("SHA3 0 -> " << h, LOG_LEVEL_0); + LOG_PRINT("SHA3 0 -> " << (scalar_t&)h, LOG_LEVEL_0); + + h = crypto::cn_fast_hash(0, 0); + LOG_PRINT("CN 0 -> " << h, LOG_LEVEL_0); + LOG_PRINT("CN 0 -> " << (scalar_t&)h, LOG_LEVEL_0); + + std::string abc("abc"); + sha3(abc.c_str(), abc.size(), &h, sizeof h); + LOG_PRINT(abc << " -> " << h, LOG_LEVEL_0); + LOG_PRINT(abc << " -> " << (scalar_t&)h, LOG_LEVEL_0); + + h = crypto::cn_fast_hash(abc.c_str(), abc.size()); + LOG_PRINT(abc << " -> " << h, LOG_LEVEL_0); + LOG_PRINT(abc << " -> " << (scalar_t&)h, LOG_LEVEL_0); + + + return true; +} + +// +// test's runner +// int crypto_tests() { @@ -867,7 +1056,19 @@ int crypto_tests() { auto& test = g_tests[i]; TIME_MEASURE_START(runtime); - bool r = test.second(); + bool r = false; + try + { + r = test.second(); + } + catch (std::exception& e) + { + LOG_PRINT_RED("EXCEPTION: " << e.what(), LOG_LEVEL_0); + } + catch (...) + { + LOG_PRINT_RED("EXCEPTION: unknown", LOG_LEVEL_0); + } TIME_MEASURE_FINISH(runtime); uint64_t runtime_ms = runtime / 1000; uint64_t runtime_mcs = runtime % 1000; diff --git a/tests/functional_tests/sha3.cpp b/tests/functional_tests/sha3.cpp new file mode 100644 index 00000000..537bb5ea --- /dev/null +++ b/tests/functional_tests/sha3.cpp @@ -0,0 +1,199 @@ +// code from tiny_sha3 project +// distibuted under MIT license: +// The MIT License(MIT) +// +// Copyright(c) 2015 Markku - Juhani O.Saarinen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files(the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions : +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +#include +#include +using namespace std; + +#ifndef KECCAKF_ROUNDS +#define KECCAKF_ROUNDS 24 +#endif + +#ifndef ROTL64 +#define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y)))) +#endif + +// state context +typedef struct { + union { // state: + uint8_t b[200]; // 8-bit bytes + uint64_t q[25]; // 64-bit words + } st; + int pt, rsiz, mdlen; // these don't overflow +} sha3_ctx_t; + + +void sha3_keccakf(uint64_t st[25]) +{ + // constants + const uint64_t keccakf_rndc[24] = { + 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, + 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, + 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, + 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, + 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, + 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, + 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, + 0x8000000000008080, 0x0000000080000001, 0x8000000080008008 + }; + const int keccakf_rotc[24] = { + 1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, + 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44 + }; + const int keccakf_piln[24] = { + 10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, + 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1 + }; + + // variables + int i, j, r; + uint64_t t, bc[5]; + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ + uint8_t *v; + + // endianess conversion. this is redundant on little-endian targets + for (i = 0; i < 25; i++) { + v = (uint8_t *)&st[i]; + st[i] = ((uint64_t)v[0]) | (((uint64_t)v[1]) << 8) | + (((uint64_t)v[2]) << 16) | (((uint64_t)v[3]) << 24) | + (((uint64_t)v[4]) << 32) | (((uint64_t)v[5]) << 40) | + (((uint64_t)v[6]) << 48) | (((uint64_t)v[7]) << 56); + } +#endif + + // actual iteration + for (r = 0; r < KECCAKF_ROUNDS; r++) { + + // Theta + for (i = 0; i < 5; i++) + bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20]; + + for (i = 0; i < 5; i++) { + t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1); + for (j = 0; j < 25; j += 5) + st[j + i] ^= t; + } + + // Rho Pi + t = st[1]; + for (i = 0; i < 24; i++) { + j = keccakf_piln[i]; + bc[0] = st[j]; + st[j] = ROTL64(t, keccakf_rotc[i]); + t = bc[0]; + } + + // Chi + for (j = 0; j < 25; j += 5) { + for (i = 0; i < 5; i++) + bc[i] = st[j + i]; + for (i = 0; i < 5; i++) + st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5]; + } + + // Iota + st[0] ^= keccakf_rndc[r]; + } + +#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__ + // endianess conversion. this is redundant on little-endian targets + for (i = 0; i < 25; i++) { + v = (uint8_t *)&st[i]; + t = st[i]; + v[0] = t & 0xFF; + v[1] = (t >> 8) & 0xFF; + v[2] = (t >> 16) & 0xFF; + v[3] = (t >> 24) & 0xFF; + v[4] = (t >> 32) & 0xFF; + v[5] = (t >> 40) & 0xFF; + v[6] = (t >> 48) & 0xFF; + v[7] = (t >> 56) & 0xFF; + } +#endif +} + +// Initialize the context for SHA3 + +int sha3_init(sha3_ctx_t *c, int mdlen) +{ + int i; + + for (i = 0; i < 25; i++) + c->st.q[i] = 0; + c->mdlen = mdlen; + c->rsiz = 200 - 2 * mdlen; + c->pt = 0; + + return 1; +} + +// update state with more data + +int sha3_update(sha3_ctx_t *c, const void *data, size_t len) +{ + size_t i; + int j; + + j = c->pt; + for (i = 0; i < len; i++) { + c->st.b[j++] ^= ((const uint8_t *)data)[i]; + if (j >= c->rsiz) { + sha3_keccakf(c->st.q); + j = 0; + } + } + c->pt = j; + + return 1; +} + +// finalize and output a hash + +int sha3_final(void *md, sha3_ctx_t *c) +{ + int i; + + c->st.b[c->pt] ^= 0x06; + c->st.b[c->rsiz - 1] ^= 0x80; + sha3_keccakf(c->st.q); + + for (i = 0; i < c->mdlen; i++) { + ((uint8_t *)md)[i] = c->st.b[i]; + } + + return 1; +} + +// compute a SHA-3 hash (md) of given byte length from "in" + +void *sha3(const void *in, size_t inlen, void *md, int mdlen) +{ + sha3_ctx_t sha3; + + sha3_init(&sha3, mdlen); + sha3_update(&sha3, in, inlen); + sha3_final(md, &sha3); + + return md; +}