1
0
Fork 0
forked from lthn/blockchain
blockchain/tests/functional_tests/crypto_tests_clsag.h

387 lines
No EOL
12 KiB
C++

// Copyright (c) 2022 Zano Project (https://zano.org/)
// Copyright (c) 2022 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/clsag.h>
inline std::ostream& operator<<(std::ostream& ss, const CLSAG_GG_signature &sig)
{
ss << "CLSAG_GG: c: " << sig.c << ENDL;
ss << " r: ";
size_t i = 0;
for(auto el: sig.r)
{
if (i++ != 0)
ss << " ";
ss << el << ENDL;
}
ss << " K1: " << sig.K1 << ENDL;
return ss;
}
struct clsag_gg_sig_check_t
{
crypto::hash prefix_hash;
crypto::key_image ki;
std::vector<public_key> stealth_addresses;
std::vector<public_key> amount_commitments; // div 8
std::vector<CLSAG_GG_input_ref_t> ring;
crypto::public_key pseudo_output_commitment; // div 8
scalar_t secret_x;
scalar_t secret_f;
size_t secret_index;
CLSAG_GG_signature sig;
clsag_gg_sig_check_t()
{}
void rebuild_ring()
{
ring.clear();
ring.reserve(stealth_addresses.size());
for(size_t i = 0; i < stealth_addresses.size(); ++i)
ring.emplace_back(stealth_addresses[i], amount_commitments[i]);
}
clsag_gg_sig_check_t& operator=(const clsag_gg_sig_check_t& rhs)
{
prefix_hash = rhs.prefix_hash;
ki = rhs.ki;
stealth_addresses = rhs.stealth_addresses;
amount_commitments = rhs.amount_commitments;
rebuild_ring();
pseudo_output_commitment = rhs.pseudo_output_commitment;
secret_x = rhs.secret_x;
secret_f = rhs.secret_f;
secret_index = rhs.secret_index;
return *this;
}
void prepare_random_data(size_t ring_size)
{
stealth_addresses.clear();
amount_commitments.clear();
ring.clear();
crypto::generate_random_bytes(sizeof prefix_hash, &prefix_hash);
stealth_addresses.reserve(ring_size);
amount_commitments.reserve(ring_size);
for(size_t i = 0; i < ring_size; ++i)
{
stealth_addresses.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key());
amount_commitments.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key()); // div 8
ring.emplace_back(stealth_addresses.back(), amount_commitments.back());
}
secret_x = scalar_t::random();
secret_f = scalar_t::random();
secret_index = random_in_range(0, ring_size - 1);
stealth_addresses[secret_index] = (secret_x * c_point_G).to_public_key();
ki = (secret_x * hash_helper_t::hp(stealth_addresses[secret_index])).to_key_image();
pseudo_output_commitment = (point_t(amount_commitments[secret_index]) - c_scalar_1div8 * secret_f * c_point_G).to_public_key();
}
bool generate()
{
try
{
return generate_CLSAG_GG(prefix_hash, ring, point_t(pseudo_output_commitment).modify_mul8(), ki, secret_x, secret_f, secret_index, sig);
}
catch(std::exception& e)
{
LOG_PRINT_RED(ENDL << "EXCEPTION: " << e.what(), LOG_LEVEL_0);
return false;
}
}
bool verify()
{
try
{
return verify_CLSAG_GG(prefix_hash, ring, pseudo_output_commitment, ki, sig);
}
catch(std::exception& e)
{
LOG_PRINT_RED(ENDL << "EXCEPTION: " << e.what(), LOG_LEVEL_0);
return false;
}
}
};
TEST(clsag, basics)
{
clsag_gg_sig_check_t cc;
cc.prepare_random_data(1);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(2);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(8);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(123);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
return true;
}
TEST(clsag, bad_pub_keys)
{
clsag_gg_sig_check_t cc, cc_orig;
cc.prepare_random_data(10);
cc_orig = cc;
ASSERT_TRUE(cc.generate());
cc.ki = parse_tpod_from_hex_string<key_image>(noncanonical_torsion_elements[0]);
ASSERT_FALSE(cc.verify());
cc.ki = parse_tpod_from_hex_string<key_image>(canonical_torsion_elements[0].string); // ki not in main subgroup
ASSERT_FALSE(cc.verify());
// check all torsion elements (paranoid mode on)
for(size_t t = 0; t < sizeof canonical_torsion_elements / sizeof canonical_torsion_elements[0]; ++t)
{
point_t tor = point_t(parse_tpod_from_hex_string<public_key>(canonical_torsion_elements[t].string));
ASSERT_FALSE(tor.is_in_main_subgroup());
ASSERT_FALSE(tor.is_zero());
// torsion component in ki must break the protocol
cc = cc_orig;
ASSERT_TRUE(cc.generate());
cc.ki = (point_t(cc.ki) + tor).to_key_image(); // ki is not in main subgroup
ASSERT_FALSE(cc.verify());
// torsion component in amount_commitments[i] should not affect protocol
cc = cc_orig;
for(size_t i = 0; i < cc.ring.size(); ++i)
cc.amount_commitments[i] = (point_t(cc.amount_commitments[i]) + tor).to_public_key();
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
// torsion component in K1 should not affect protocol
cc = cc_orig;
ASSERT_TRUE(cc.generate());
cc.sig.K1 = (point_t(cc.sig.K1) + tor).to_public_key();
ASSERT_TRUE(cc.verify());
// torsion component in stealth_address for secret_index (i.e. for P = xG) must break the protocol
// 1
cc = cc_orig;
cc.stealth_addresses[cc.secret_index] = (point_t(cc.stealth_addresses[cc.secret_index]) + tor).to_public_key();
ASSERT_FALSE(cc.generate());
// 2
cc = cc_orig;
ASSERT_TRUE(cc.generate());
cc.stealth_addresses[cc.secret_index] = (point_t(cc.stealth_addresses[cc.secret_index]) + tor).to_public_key();
ASSERT_FALSE(cc.verify());
// torsion component in pseudo_output_commitment must break the protocol
cc = cc_orig;
cc.pseudo_output_commitment = (point_t(cc.pseudo_output_commitment) + tor).to_public_key();
ASSERT_TRUE(cc.generate());
ASSERT_FALSE(cc.verify());
}
return true;
}
TEST(clsag, bad_sig)
{
clsag_gg_sig_check_t cc;
// wrong prefix hash
cc.prepare_random_data(10);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prefix_hash.data[5] ^= 0x7f;
ASSERT_FALSE(cc.verify());
// ring size > sig.r size
ASSERT_TRUE(cc.generate());
cc.sig.r.clear();
ASSERT_FALSE(cc.verify());
// ring size < sig.r size
ASSERT_TRUE(cc.generate());
cc.sig.r.push_back(scalar_t::random());
ASSERT_FALSE(cc.verify());
return true;
}
TEST(clsag, sig_difference)
{
int cmp_res = 0;
clsag_gg_sig_check_t cc0, cc1;
cc0.prepare_random_data(8);
cc1 = cc0; // get the same input data
{
// make sure two signatures generated with the same input and randoms are equal
random_state_test_restorer rstr; // to restore random state on exit of the scope
random_state_test_restorer::reset_random(0);
ASSERT_TRUE(cc0.generate());
random_state_test_restorer::reset_random(0);
ASSERT_TRUE(cc1.generate());
LOG_PRINT_L0("cc0: " << ENDL << cc0.sig << ", cc1: " << ENDL << cc1.sig);
ASSERT_TRUE(cc0.sig == cc1.sig);
ASSERT_TRUE(cc0.verify());
}
// make sure two signatures generated with the same input are not equal
ASSERT_TRUE(cc0.generate());
ASSERT_TRUE(cc1.generate());
ASSERT_TRUE(cc0.sig != cc1.sig);
return true;
}
//
// CLSAG GGXG
//
struct clsag_ggxg_sig_check_t
{
crypto::hash prefix_hash;
crypto::key_image ki;
std::vector<public_key> stealth_addresses;
std::vector<public_key> amount_commitments; // div 8
std::vector<public_key> concealing_points; // div 8
std::vector<CLSAG_GGXG_input_ref_t> ring;
crypto::public_key pseudo_output_commitment; // div 8
crypto::public_key extended_amount_commitment; // div 8
scalar_t secret_xp;
scalar_t secret_f; // = f - f' = amount_blinding_mask - pseudo_commitment_blinding_mask
scalar_t secret_x;
scalar_t secret_q;
size_t secret_index;
CLSAG_GGXG_signature sig;
clsag_ggxg_sig_check_t()
{}
void rebuild_ring()
{
ring.clear();
ring.reserve(stealth_addresses.size());
for(size_t i = 0; i < stealth_addresses.size(); ++i)
ring.emplace_back(stealth_addresses[i], amount_commitments[i], concealing_points[i]);
}
clsag_ggxg_sig_check_t& operator=(const clsag_ggxg_sig_check_t& rhs)
{
prefix_hash = rhs.prefix_hash;
ki = rhs.ki;
stealth_addresses = rhs.stealth_addresses;
amount_commitments = rhs.amount_commitments;
concealing_points = rhs.concealing_points;
rebuild_ring();
pseudo_output_commitment = rhs.pseudo_output_commitment;
extended_amount_commitment = rhs.extended_amount_commitment;
secret_xp = rhs.secret_xp;
secret_f = rhs.secret_f;
secret_x = rhs.secret_x;
secret_q = rhs.secret_q;
secret_index = rhs.secret_index;
return *this;
}
void prepare_random_data(size_t ring_size)
{
stealth_addresses.clear();
amount_commitments.clear();
concealing_points.clear();
ring.clear();
crypto::generate_random_bytes(sizeof prefix_hash, &prefix_hash);
stealth_addresses.reserve(ring_size);
amount_commitments.reserve(ring_size);
concealing_points.reserve(ring_size);
for(size_t i = 0; i < ring_size; ++i)
{
stealth_addresses.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key());
amount_commitments.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key()); // div 8
concealing_points.push_back(hash_helper_t::hp(scalar_t::random()).to_public_key()); // div 8
ring.emplace_back(stealth_addresses.back(), amount_commitments.back(), concealing_points.back());
}
secret_xp = scalar_t::random();
secret_f = scalar_t::random();
secret_x = scalar_t::random();
secret_q = scalar_t::random();
secret_index = random_in_range(0, ring_size - 1);
stealth_addresses[secret_index] = (secret_xp * c_point_G).to_public_key();
concealing_points[secret_index] = (c_scalar_1div8 * secret_q * c_point_G).to_public_key();
ki = (secret_xp * hash_helper_t::hp(stealth_addresses[secret_index])).to_key_image();
pseudo_output_commitment = (point_t(amount_commitments[secret_index]) - c_scalar_1div8 * secret_f * c_point_G).to_public_key();
extended_amount_commitment = (c_scalar_1div8 * secret_x * c_point_X + point_t(amount_commitments[secret_index]) + point_t(concealing_points[secret_index])).to_public_key();
}
bool generate()
{
try
{
return generate_CLSAG_GGXG(prefix_hash, ring, point_t(pseudo_output_commitment).modify_mul8(), point_t(extended_amount_commitment).modify_mul8(), ki,
secret_xp, secret_f, secret_x, secret_q, secret_index, sig);
}
catch(std::exception& e)
{
LOG_PRINT_RED(ENDL << "EXCEPTION: " << e.what(), LOG_LEVEL_0);
return false;
}
}
bool verify()
{
try
{
return verify_CLSAG_GGXG(prefix_hash, ring, pseudo_output_commitment, extended_amount_commitment, ki, sig);
}
catch(std::exception& e)
{
LOG_PRINT_RED(ENDL << "EXCEPTION: " << e.what(), LOG_LEVEL_0);
return false;
}
}
};
TEST(clsag_ggxg, basics)
{
std::string X_hash_str("X_generator");
point_t X = hash_helper_t::hp(X_hash_str.c_str(), X_hash_str.size());
LOG_PRINT_L0("X = " << X.to_hex_comma_separated_uint64_str());
ASSERT_EQ(X, c_point_X);
clsag_ggxg_sig_check_t cc;
cc.prepare_random_data(1);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(2);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(8);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
cc.prepare_random_data(123);
ASSERT_TRUE(cc.generate());
ASSERT_TRUE(cc.verify());
return true;
}