mirror of
https://github.com/monero-project/monero.git
synced 2024-12-23 03:59:33 +00:00
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:
parent
5d850dde99
commit
6bfcd31015
7 changed files with 332 additions and 31 deletions
|
@ -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,10 +457,20 @@ 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)
|
||||||
{
|
{
|
||||||
// compute X = k*B
|
// compute X = k*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);
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
130
tests/unit_tests/tx_proof.cpp
Normal file
130
tests/unit_tests/tx_proof.cpp
Normal 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));
|
||||||
|
}
|
Loading…
Reference in a new issue