Updates InProofV1, OutProofV1, and ReserveProofV1 to new V2 variants that include all public proof parameters in Schnorr challenges, along with hash function domain separators. Includes new randomized unit tests.

This commit is contained in:
Sarang Noether 2020-08-09 18:42:15 -04:00
parent 5d850dde99
commit 6bfcd31015
7 changed files with 332 additions and 31 deletions

View file

@ -43,6 +43,8 @@
#include "crypto.h" #include "crypto.h"
#include "hash.h" #include "hash.h"
#include "cryptonote_config.h"
namespace { namespace {
static void local_abort(const char *msg) static void local_abort(const char *msg)
{ {
@ -261,11 +263,24 @@ namespace crypto {
ec_point comm; ec_point comm;
}; };
// Used in v1 tx proofs
struct s_comm_2_v1 {
hash msg;
ec_point D;
ec_point X;
ec_point Y;
};
// Used in v1/v2 tx proofs
struct s_comm_2 { struct s_comm_2 {
hash msg; hash msg;
ec_point D; ec_point D;
ec_point X; ec_point X;
ec_point Y; ec_point Y;
hash sep; // domain separation
ec_point R;
ec_point A;
ec_point B;
}; };
void crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) { void crypto_ops::generate_signature(const hash &prefix_hash, const public_key &pub, const secret_key &sec, signature &sig) {
@ -321,6 +336,86 @@ namespace crypto {
return sc_isnonzero(&c) == 0; return sc_isnonzero(&c) == 0;
} }
// Generate a proof of knowledge of `r` such that (`R = rG` and `D = rA`) or (`R = rB` and `D = rA`) via a Schnorr proof
// This handles use cases for both standard addresses and subaddresses
//
// NOTE: This generates old v1 proofs, and is for TESTING ONLY
void crypto_ops::generate_tx_proof_v1(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
// sanity check
ge_p3 R_p3;
ge_p3 A_p3;
ge_p3 B_p3;
ge_p3 D_p3;
if (ge_frombytes_vartime(&R_p3, &R) != 0) throw std::runtime_error("tx pubkey is invalid");
if (ge_frombytes_vartime(&A_p3, &A) != 0) throw std::runtime_error("recipient view pubkey is invalid");
if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) throw std::runtime_error("recipient spend pubkey is invalid");
if (ge_frombytes_vartime(&D_p3, &D) != 0) throw std::runtime_error("key derivation is invalid");
#if !defined(NDEBUG)
{
assert(sc_check(&r) == 0);
// check R == r*G or R == r*B
public_key dbg_R;
if (B)
{
ge_p2 dbg_R_p2;
ge_scalarmult(&dbg_R_p2, &r, &B_p3);
ge_tobytes(&dbg_R, &dbg_R_p2);
}
else
{
ge_p3 dbg_R_p3;
ge_scalarmult_base(&dbg_R_p3, &r);
ge_p3_tobytes(&dbg_R, &dbg_R_p3);
}
assert(R == dbg_R);
// check D == r*A
ge_p2 dbg_D_p2;
ge_scalarmult(&dbg_D_p2, &r, &A_p3);
public_key dbg_D;
ge_tobytes(&dbg_D, &dbg_D_p2);
assert(D == dbg_D);
}
#endif
// pick random k
ec_scalar k;
random_scalar(k);
s_comm_2_v1 buf;
buf.msg = prefix_hash;
buf.D = D;
if (B)
{
// compute X = k*B
ge_p2 X_p2;
ge_scalarmult(&X_p2, &k, &B_p3);
ge_tobytes(&buf.X, &X_p2);
}
else
{
// compute X = k*G
ge_p3 X_p3;
ge_scalarmult_base(&X_p3, &k);
ge_p3_tobytes(&buf.X, &X_p3);
}
// compute Y = k*A
ge_p2 Y_p2;
ge_scalarmult(&Y_p2, &k, &A_p3);
ge_tobytes(&buf.Y, &Y_p2);
// sig.c = Hs(Msg || D || X || Y)
hash_to_scalar(&buf, sizeof(buf), sig.c);
// sig.r = k - sig.c*r
sc_mulsub(&sig.r, &sig.c, &unwrap(r), &k);
}
// Generate a proof of knowledge of `r` such that (`R = rG` and `D = rA`) or (`R = rB` and `D = rA`) via a Schnorr proof
// This handles use cases for both standard addresses and subaddresses
//
// Generates only proofs for InProofV2 and OutProofV2
void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) { void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
// sanity check // sanity check
ge_p3 R_p3; ge_p3 R_p3;
@ -362,9 +457,19 @@ namespace crypto {
ec_scalar k; ec_scalar k;
random_scalar(k); random_scalar(k);
// if B is not present
static const ec_point zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }};
s_comm_2 buf; s_comm_2 buf;
buf.msg = prefix_hash; buf.msg = prefix_hash;
buf.D = D; buf.D = D;
buf.R = R;
buf.A = A;
if (B)
buf.B = *B;
else
buf.B = zero;
cn_fast_hash(config::HASH_KEY_TXPROOF_V2, sizeof(config::HASH_KEY_TXPROOF_V2)-1, buf.sep);
if (B) if (B)
{ {
@ -386,7 +491,7 @@ namespace crypto {
ge_scalarmult(&Y_p2, &k, &A_p3); ge_scalarmult(&Y_p2, &k, &A_p3);
ge_tobytes(&buf.Y, &Y_p2); ge_tobytes(&buf.Y, &Y_p2);
// sig.c = Hs(Msg || D || X || Y) // sig.c = Hs(Msg || D || X || Y || sep || R || A || B)
hash_to_scalar(&buf, sizeof(buf), sig.c); hash_to_scalar(&buf, sizeof(buf), sig.c);
// sig.r = k - sig.c*r // sig.r = k - sig.c*r
@ -395,7 +500,8 @@ namespace crypto {
memwipe(&k, sizeof(k)); memwipe(&k, sizeof(k));
} }
bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) { // Verify a proof: either v1 (version == 1) or v2 (version == 2)
bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig, const int version) {
// sanity check // sanity check
ge_p3 R_p3; ge_p3 R_p3;
ge_p3 A_p3; ge_p3 A_p3;
@ -467,14 +573,31 @@ namespace crypto {
ge_p2 Y_p2; ge_p2 Y_p2;
ge_p1p1_to_p2(&Y_p2, &Y_p1p1); ge_p1p1_to_p2(&Y_p2, &Y_p1p1);
// compute c2 = Hs(Msg || D || X || Y) // Compute hash challenge
// for v1, c2 = Hs(Msg || D || X || Y)
// for v2, c2 = Hs(Msg || D || X || Y || sep || R || A || B)
// if B is not present
static const ec_point zero = {{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }};
s_comm_2 buf; s_comm_2 buf;
buf.msg = prefix_hash; buf.msg = prefix_hash;
buf.D = D; buf.D = D;
buf.R = R;
buf.A = A;
if (B)
buf.B = *B;
else
buf.B = zero;
cn_fast_hash(config::HASH_KEY_TXPROOF_V2, sizeof(config::HASH_KEY_TXPROOF_V2)-1, buf.sep);
ge_tobytes(&buf.X, &X_p2); ge_tobytes(&buf.X, &X_p2);
ge_tobytes(&buf.Y, &Y_p2); ge_tobytes(&buf.Y, &Y_p2);
ec_scalar c2; ec_scalar c2;
hash_to_scalar(&buf, sizeof(s_comm_2), c2);
// Hash depends on version
if (version == 1) hash_to_scalar(&buf, sizeof(s_comm_2) - 3*sizeof(ec_point) - sizeof(hash), c2);
else if (version == 2) hash_to_scalar(&buf, sizeof(s_comm_2), c2);
else return false;
// test if c2 == sig.c // test if c2 == sig.c
sc_sub(&c2, &c2, &sig.c); sc_sub(&c2, &c2, &sig.c);

View file

@ -132,8 +132,10 @@ namespace crypto {
friend bool check_signature(const hash &, const public_key &, const signature &); friend bool check_signature(const hash &, const public_key &, const signature &);
static void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &); static void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &); friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &); static void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &); friend void generate_tx_proof_v1(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const secret_key &, signature &);
static bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &, const int);
friend bool check_tx_proof(const hash &, const public_key &, const public_key &, const boost::optional<public_key> &, const public_key &, const signature &, const int);
static void generate_key_image(const public_key &, const secret_key &, key_image &); static void generate_key_image(const public_key &, const secret_key &, key_image &);
friend void generate_key_image(const public_key &, const secret_key &, key_image &); friend void generate_key_image(const public_key &, const secret_key &, key_image &);
static void generate_ring_signature(const hash &, const key_image &, static void generate_ring_signature(const hash &, const key_image &,
@ -248,8 +250,11 @@ namespace crypto {
inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) { inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
crypto_ops::generate_tx_proof(prefix_hash, R, A, B, D, r, sig); crypto_ops::generate_tx_proof(prefix_hash, R, A, B, D, r, sig);
} }
inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig) { inline void generate_tx_proof_v1(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const secret_key &r, signature &sig) {
return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig); crypto_ops::generate_tx_proof_v1(prefix_hash, R, A, B, D, r, sig);
}
inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const boost::optional<public_key> &B, const public_key &D, const signature &sig, const int version) {
return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig, version);
} }
/* To send money to a key: /* To send money to a key:

View file

@ -219,6 +219,7 @@ namespace config
const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58;
const unsigned char HASH_KEY_MEMORY = 'k'; const unsigned char HASH_KEY_MEMORY = 'k';
const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
const unsigned char HASH_KEY_TXPROOF_V2[] = "TXPROOF_V2";
namespace testnet namespace testnet
{ {

View file

@ -11425,7 +11425,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]); hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
} }
} }
sig_str = std::string("OutProofV1"); sig_str = std::string("OutProofV2");
} }
else else
{ {
@ -11461,7 +11461,7 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]); hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]);
} }
} }
sig_str = std::string("InProofV1"); sig_str = std::string("InProofV2");
} }
const size_t num_sigs = shared_secret.size(); const size_t num_sigs = shared_secret.size();
@ -11540,8 +11540,14 @@ bool wallet2::check_tx_proof(const crypto::hash &txid, const cryptonote::account
bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received) const bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message, const std::string &sig_str, uint64_t &received) const
{ {
// InProofV1, InProofV2, OutProofV1, OutProofV2
const bool is_out = sig_str.substr(0, 3) == "Out"; const bool is_out = sig_str.substr(0, 3) == "Out";
const std::string header = is_out ? "OutProofV1" : "InProofV1"; const std::string header = is_out ? sig_str.substr(0,10) : sig_str.substr(0,9);
int version = 2; // InProofV2
if (is_out && sig_str.substr(8,2) == "V1") version = 1; // OutProofV1
else if (is_out) version = 2; // OutProofV2
else if (sig_str.substr(7,2) == "V1") version = 1; // InProofV1
const size_t header_len = header.size(); const size_t header_len = header.size();
THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(sig_str.size() < header_len || sig_str.substr(0, header_len) != header, error::wallet_internal_error,
"Signature header check error"); "Signature header check error");
@ -11588,27 +11594,27 @@ bool wallet2::check_tx_proof(const cryptonote::transaction &tx, const cryptonote
if (is_out) if (is_out)
{ {
good_signature[0] = is_subaddress ? good_signature[0] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0]) : crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0]); crypto::check_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], sig[0], version);
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{ {
good_signature[i + 1] = is_subaddress ? good_signature[i + 1] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) :
crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1]); crypto::check_tx_proof(prefix_hash, additional_tx_pub_keys[i], address.m_view_public_key, boost::none, shared_secret[i + 1], sig[i + 1], version);
} }
} }
else else
{ {
good_signature[0] = is_subaddress ? good_signature[0] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0]) : crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], sig[0], version) :
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0]); crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], sig[0], version);
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i) for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{ {
good_signature[i + 1] = is_subaddress ? good_signature[i + 1] = is_subaddress ?
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1]) : crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], address.m_spend_public_key, shared_secret[i + 1], sig[i + 1], version) :
crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1]); crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i], boost::none, shared_secret[i + 1], sig[i + 1], version);
} }
} }
@ -11746,7 +11752,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t,
std::ostringstream oss; std::ostringstream oss;
boost::archive::portable_binary_oarchive ar(oss); boost::archive::portable_binary_oarchive ar(oss);
ar << proofs << subaddr_spendkeys; ar << proofs << subaddr_spendkeys;
return "ReserveProofV1" + tools::base58::encode(oss.str()); return "ReserveProofV2" + tools::base58::encode(oss.str());
} }
bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent) bool wallet2::check_reserve_proof(const cryptonote::account_public_address &address, const std::string &message, const std::string &sig_str, uint64_t &total, uint64_t &spent)
@ -11755,12 +11761,18 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address()); THROW_WALLET_EXCEPTION_IF(!check_connection(&rpc_version), error::wallet_internal_error, "Failed to connect to daemon: " + get_daemon_address());
THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old"); THROW_WALLET_EXCEPTION_IF(rpc_version < MAKE_CORE_RPC_VERSION(1, 0), error::wallet_internal_error, "Daemon RPC version is too old");
static constexpr char header[] = "ReserveProofV1"; static constexpr char header_v1[] = "ReserveProofV1";
THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header), error::wallet_internal_error, static constexpr char header_v2[] = "ReserveProofV2"; // assumes same length as header_v1
THROW_WALLET_EXCEPTION_IF(!boost::string_ref{sig_str}.starts_with(header_v1) && !boost::string_ref{sig_str}.starts_with(header_v2), error::wallet_internal_error,
"Signature header check error"); "Signature header check error");
int version = 2; // assume newest version
if (boost::string_ref{sig_str}.starts_with(header_v1))
version = 1;
else if (boost::string_ref{sig_str}.starts_with(header_v2))
version = 2;
std::string sig_decoded; std::string sig_decoded;
THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header)), sig_decoded), error::wallet_internal_error, THROW_WALLET_EXCEPTION_IF(!tools::base58::decode(sig_str.substr(std::strlen(header_v1)), sig_decoded), error::wallet_internal_error,
"Signature decoding error"); "Signature decoding error");
std::istringstream iss(sig_decoded); std::istringstream iss(sig_decoded);
@ -11841,9 +11853,9 @@ bool wallet2::check_reserve_proof(const cryptonote::account_public_address &addr
const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx); const std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
// check singature for shared secret // check singature for shared secret
ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig); ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, proof.shared_secret, proof.shared_secret_sig, version);
if (!ok && additional_tx_pub_keys.size() == tx.vout.size()) if (!ok && additional_tx_pub_keys.size() == tx.vout.size())
ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig); ok = crypto::check_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[proof.index_in_tx], boost::none, proof.shared_secret, proof.shared_secret_sig, version);
if (!ok) if (!ok)
return false; return false;

View file

@ -130,13 +130,13 @@ class ProofsTest():
sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo'); res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo');
assert res.signature.startswith('InProof'); assert res.signature.startswith('InProofV2');
signature0i = res.signature signature0i = res.signature
res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar'); res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar');
assert res.signature.startswith('OutProof'); assert res.signature.startswith('OutProofV2');
signature0o = res.signature signature0o = res.signature
res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz'); res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz');
assert res.signature.startswith('InProof'); assert res.signature.startswith('InProofV2');
signature1 = res.signature signature1 = res.signature
res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i); res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i);
@ -219,6 +219,23 @@ class ProofsTest():
except: ok = True except: ok = True
assert ok or not res.good assert ok or not res.good
# Test bad cross-version verification
ok = False
try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i.replace('ProofV2','ProofV1'));
except: ok = True
assert ok or not res.good
ok = False
try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0o.replace('ProofV2','ProofV1'));
except: ok = True
assert ok or not res.good
ok = False
try: res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature1.replace('ProofV2','ProofV1'));
except: ok = True
assert ok or not res.good
def check_spend_proof(self, txid): def check_spend_proof(self, txid):
daemon = Daemon() daemon = Daemon()
@ -270,7 +287,7 @@ class ProofsTest():
balance1 = res.balance balance1 = res.balance
res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo') res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo')
assert res.signature.startswith('ReserveProof') assert res.signature.startswith('ReserveProofV2')
signature = res.signature signature = res.signature
for i in range(2): for i in range(2):
res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature) res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
@ -287,9 +304,15 @@ class ProofsTest():
except: ok = True except: ok = True
assert ok or not res.good assert ok or not res.good
# Test bad cross-version verification
ok = False
try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature.replace('ProofV2','ProofV1'))
except: ok = True
assert ok or not res.good
amount = int(balance0 / 10) amount = int(balance0 / 10)
res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo') res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo')
assert res.signature.startswith('ReserveProof') assert res.signature.startswith('ReserveProofV2')
signature = res.signature signature = res.signature
for i in range(2): for i in range(2):
res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature) res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
@ -306,6 +329,12 @@ class ProofsTest():
except: ok = True except: ok = True
assert ok or not res.good assert ok or not res.good
# Test bad cross-version verification
ok = False
try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature.replace('ProofV2','ProofV1'))
except: ok = True
assert ok or not res.good
ok = False ok = False
try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo') try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo')
except: ok = True except: ok = True

View file

@ -83,6 +83,7 @@ set(unit_tests_sources
test_peerlist.cpp test_peerlist.cpp
test_protocol_pack.cpp test_protocol_pack.cpp
threadpool.cpp threadpool.cpp
tx_proof.cpp
hardfork.cpp hardfork.cpp
unbound.cpp unbound.cpp
uri.cpp uri.cpp

View file

@ -0,0 +1,130 @@
// Copyright (c) 2018, The Monero Project
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification, are
// permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this list of
// conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice, this list
// of conditions and the following disclaimer in the documentation and/or other
// materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its contributors may be
// used to endorse or promote products derived from this software without specific
// prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "gtest/gtest.h"
#include "crypto/crypto.h"
extern "C" {
#include "crypto/crypto-ops.h"
}
#include "crypto/hash.h"
#include <boost/algorithm/string.hpp>
static inline unsigned char *operator &(crypto::ec_point &point) {
return &reinterpret_cast<unsigned char &>(point);
}
static inline unsigned char *operator &(crypto::ec_scalar &scalar) {
return &reinterpret_cast<unsigned char &>(scalar);
}
TEST(tx_proof, prove_verify_v2)
{
crypto::secret_key r;
crypto::random32_unbiased(&r);
// A = aG
// B = bG
crypto::secret_key a,b;
crypto::public_key A,B;
crypto::generate_keys(A, a, a, false);
crypto::generate_keys(B, b, b, false);
// R_B = rB
crypto::public_key R_B;
ge_p3 B_p3;
ge_frombytes_vartime(&B_p3,&B);
ge_p2 R_B_p2;
ge_scalarmult(&R_B_p2, &unwrap(r), &B_p3);
ge_tobytes(&R_B, &R_B_p2);
// R_G = rG
crypto::public_key R_G;
ge_frombytes_vartime(&B_p3,&B);
ge_p3 R_G_p3;
ge_scalarmult_base(&R_G_p3, &unwrap(r));
ge_p3_tobytes(&R_G, &R_G_p3);
// D = rA
crypto::public_key D;
ge_p3 A_p3;
ge_frombytes_vartime(&A_p3,&A);
ge_p2 D_p2;
ge_scalarmult(&D_p2, &unwrap(r), &A_p3);
ge_tobytes(&D, &D_p2);
crypto::signature sig;
// Message data
crypto::hash prefix_hash;
char data[] = "hash input";
crypto::cn_fast_hash(data,sizeof(data)-1,prefix_hash);
// Generate/verify valid v1 proof with standard address
crypto::generate_tx_proof_v1(prefix_hash, R_G, A, boost::none, D, r, sig);
ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 1));
// Generate/verify valid v1 proof with subaddress
crypto::generate_tx_proof_v1(prefix_hash, R_B, A, B, D, r, sig);
ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 1));
// Generate/verify valid v2 proof with standard address
crypto::generate_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig);
ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 2));
// Generate/verify valid v2 proof with subaddress
crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig);
ASSERT_TRUE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 2));
// Try to verify valid v2 proofs as v1 proof (bad)
crypto::generate_tx_proof(prefix_hash, R_G, A, boost::none, D, r, sig);
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 1));
crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig);
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 1));
// Randomly-distributed test points
crypto::secret_key evil_a, evil_b, evil_d, evil_r;
crypto::public_key evil_A, evil_B, evil_D, evil_R;
crypto::generate_keys(evil_A, evil_a, evil_a, false);
crypto::generate_keys(evil_B, evil_b, evil_b, false);
crypto::generate_keys(evil_D, evil_d, evil_d, false);
crypto::generate_keys(evil_R, evil_r, evil_r, false);
// Selectively choose bad point in v2 proof (bad)
crypto::generate_tx_proof(prefix_hash, R_B, A, B, D, r, sig);
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, evil_R, A, B, D, sig, 2));
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, evil_A, B, D, sig, 2));
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, evil_B, D, sig, 2));
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, evil_D, sig, 2));
// Try to verify valid v1 proofs as v2 proof (bad)
crypto::generate_tx_proof_v1(prefix_hash, R_G, A, boost::none, D, r, sig);
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_G, A, boost::none, D, sig, 2));
crypto::generate_tx_proof_v1(prefix_hash, R_B, A, B, D, r, sig);
ASSERT_FALSE(crypto::check_tx_proof(prefix_hash, R_B, A, B, D, sig, 2));
}