aboutsummaryrefslogtreecommitdiff
path: root/src/crypto
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/crypto/crypto.cpp125
-rw-r--r--src/crypto/crypto.h27
-rw-r--r--src/cryptonote_basic/account.cpp2
-rw-r--r--src/cryptonote_basic/cryptonote_basic.h26
-rw-r--r--src/cryptonote_basic/cryptonote_basic_impl.cpp76
-rw-r--r--src/cryptonote_basic/cryptonote_basic_impl.h30
-rw-r--r--src/cryptonote_basic/cryptonote_boost_serialization.h5
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.cpp138
-rw-r--r--src/cryptonote_basic/cryptonote_format_utils.h20
-rw-r--r--src/cryptonote_basic/miner.cpp4
-rw-r--r--src/cryptonote_basic/subaddress_index.h102
-rw-r--r--src/cryptonote_basic/tx_extra.h14
-rw-r--r--src/cryptonote_config.h2
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.cpp103
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h20
15 files changed, 561 insertions, 133 deletions
diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp
index 5fb670f87..95ba34828 100644
--- a/src/crypto/crypto.cpp
+++ b/src/crypto/crypto.cpp
@@ -87,7 +87,7 @@ namespace crypto {
random_scalar_not_thread_safe(res);
}
- static inline void hash_to_scalar(const void *data, size_t length, ec_scalar &res) {
+ void hash_to_scalar(const void *data, size_t length, ec_scalar &res) {
cn_fast_hash(data, length, reinterpret_cast<hash &>(res));
sc_reduce32(&res);
}
@@ -189,6 +189,25 @@ namespace crypto {
sc_add(&derived_key, &base, &scalar);
}
+ bool crypto_ops::derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &derived_key) {
+ ec_scalar scalar;
+ ge_p3 point1;
+ ge_p3 point2;
+ ge_cached point3;
+ ge_p1p1 point4;
+ ge_p2 point5;
+ if (ge_frombytes_vartime(&point1, &out_key) != 0) {
+ return false;
+ }
+ derivation_to_scalar(derivation, output_index, scalar);
+ ge_scalarmult_base(&point2, &scalar);
+ ge_p3_to_cached(&point3, &point2);
+ ge_sub(&point4, &point1, &point3);
+ ge_p1p1_to_p2(&point5, &point4);
+ ge_tobytes(&derived_key, &point5);
+ return true;
+ }
+
struct s_comm {
hash h;
ec_point key;
@@ -246,22 +265,33 @@ namespace crypto {
return sc_isnonzero(&c) == 0;
}
- void crypto_ops::generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, 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
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
- ge_p3 dbg_R_p3;
- ge_scalarmult_base(&dbg_R_p3, &r);
+ // check R == r*G or R == r*B
public_key dbg_R;
- ge_p3_tobytes(&dbg_R, &dbg_R_p3);
+ 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;
@@ -276,43 +306,84 @@ namespace crypto {
ec_scalar k;
random_scalar(k);
- // compute X = k*G
- ge_p3 X_p3;
- ge_scalarmult_base(&X_p3, &k);
+ s_comm_2 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)
- s_comm_2 buf;
- buf.msg = prefix_hash;
- buf.D = D;
- ge_p3_tobytes(&buf.X, &X_p3);
- ge_tobytes(&buf.Y, &Y_p2);
- hash_to_scalar(&buf, sizeof(s_comm_2), sig.c);
+ hash_to_scalar(&buf, sizeof(buf), sig.c);
// sig.r = k - sig.c*r
sc_mulsub(&sig.r, &sig.c, &r, &k);
}
- bool crypto_ops::check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) {
+ 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) {
// 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) return false;
if (ge_frombytes_vartime(&A_p3, &A) != 0) return false;
+ if (B && ge_frombytes_vartime(&B_p3, &*B) != 0) return false;
if (ge_frombytes_vartime(&D_p3, &D) != 0) return false;
if (sc_check(&sig.c) != 0 || sc_check(&sig.r) != 0) return false;
// compute sig.c*R
- ge_p2 cR_p2;
- ge_scalarmult(&cR_p2, &sig.c, &R_p3);
+ ge_p3 cR_p3;
+ {
+ ge_p2 cR_p2;
+ ge_scalarmult(&cR_p2, &sig.c, &R_p3);
+ public_key cR;
+ ge_tobytes(&cR, &cR_p2);
+ if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false;
+ }
- // compute sig.r*G
- ge_p3 rG_p3;
- ge_scalarmult_base(&rG_p3, &sig.r);
+ ge_p1p1 X_p1p1;
+ if (B)
+ {
+ // compute X = sig.c*R + sig.r*B
+ ge_p2 rB_p2;
+ ge_scalarmult(&rB_p2, &sig.r, &B_p3);
+ public_key rB;
+ ge_tobytes(&rB, &rB_p2);
+ ge_p3 rB_p3;
+ if (ge_frombytes_vartime(&rB_p3, &rB) != 0) return false;
+ ge_cached rB_cached;
+ ge_p3_to_cached(&rB_cached, &rB_p3);
+ ge_add(&X_p1p1, &cR_p3, &rB_cached);
+ }
+ else
+ {
+ // compute X = sig.c*R + sig.r*G
+ ge_p3 rG_p3;
+ ge_scalarmult_base(&rG_p3, &sig.r);
+ ge_cached rG_cached;
+ ge_p3_to_cached(&rG_cached, &rG_p3);
+ ge_add(&X_p1p1, &cR_p3, &rG_cached);
+ }
+ ge_p2 X_p2;
+ ge_p1p1_to_p2(&X_p2, &X_p1p1);
// compute sig.c*D
ge_p2 cD_p2;
@@ -322,18 +393,6 @@ namespace crypto {
ge_p2 rA_p2;
ge_scalarmult(&rA_p2, &sig.r, &A_p3);
- // compute X = sig.c*R + sig.r*G
- public_key cR;
- ge_tobytes(&cR, &cR_p2);
- ge_p3 cR_p3;
- if (ge_frombytes_vartime(&cR_p3, &cR) != 0) return false;
- ge_cached rG_cached;
- ge_p3_to_cached(&rG_cached, &rG_p3);
- ge_p1p1 X_p1p1;
- ge_add(&X_p1p1, &cR_p3, &rG_cached);
- ge_p2 X_p2;
- ge_p1p1_to_p2(&X_p2, &X_p1p1);
-
// compute Y = sig.c*D + sig.r*A
public_key cD;
public_key rA;
diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h
index 94f924296..abdea0165 100644
--- a/src/crypto/crypto.h
+++ b/src/crypto/crypto.h
@@ -35,6 +35,7 @@
#include <boost/thread/mutex.hpp>
#include <boost/thread/lock_guard.hpp>
#include <boost/utility/value_init.hpp>
+#include <boost/optional.hpp>
#include <vector>
#include "common/pod-class.h"
@@ -98,6 +99,8 @@ namespace crypto {
};
#pragma pack(pop)
+ void hash_to_scalar(const void *data, size_t length, ec_scalar &res);
+
static_assert(sizeof(ec_point) == 32 && sizeof(ec_scalar) == 32 &&
sizeof(public_key) == 32 && sizeof(secret_key) == 32 &&
sizeof(key_derivation) == 32 && sizeof(key_image) == 32 &&
@@ -123,14 +126,16 @@ namespace crypto {
friend bool derive_public_key(const key_derivation &, std::size_t, const public_key &, public_key &);
static void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &);
friend void derive_secret_key(const key_derivation &, std::size_t, const secret_key &, secret_key &);
+ static bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &);
+ friend bool derive_subaddress_public_key(const public_key &, const key_derivation &, std::size_t, public_key &);
static void generate_signature(const hash &, const public_key &, const secret_key &, signature &);
friend void generate_signature(const hash &, const public_key &, const secret_key &, signature &);
static 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 public_key &, const secret_key &, signature &);
- friend void generate_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const secret_key &, signature &);
- static bool check_tx_proof(const hash &, const public_key &, const public_key &, const public_key &, const signature &);
- friend bool check_tx_proof(const hash &, const public_key &, const public_key &, 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 &);
+ 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 &);
+ friend 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_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 &,
@@ -198,6 +203,9 @@ namespace crypto {
const secret_key &base, secret_key &derived_key) {
crypto_ops::derive_secret_key(derivation, output_index, base, derived_key);
}
+ inline bool derive_subaddress_public_key(const public_key &out_key, const key_derivation &derivation, std::size_t output_index, public_key &result) {
+ return crypto_ops::derive_subaddress_public_key(out_key, derivation, output_index, result);
+ }
/* Generation and checking of a standard signature.
*/
@@ -210,12 +218,13 @@ namespace crypto {
/* Generation and checking of a tx proof; given a tx pubkey R, the recipient's view pubkey A, and the key
* derivation D, the signature proves the knowledge of the tx secret key r such that R=r*G and D=r*A
+ * When the recipient's address is a subaddress, the tx pubkey R is defined as R=r*B where B is the recipient's spend pubkey
*/
- inline void generate_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const secret_key &r, signature &sig) {
- crypto_ops::generate_tx_proof(prefix_hash, R, A, D, r, 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);
}
- inline bool check_tx_proof(const hash &prefix_hash, const public_key &R, const public_key &A, const public_key &D, const signature &sig) {
- return crypto_ops::check_tx_proof(prefix_hash, R, A, D, 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) {
+ return crypto_ops::check_tx_proof(prefix_hash, R, A, B, D, sig);
}
/* To send money to a key:
@@ -270,8 +279,10 @@ namespace crypto {
}
const static crypto::public_key null_pkey = boost::value_initialized<crypto::public_key>();
+ const static crypto::secret_key null_skey = boost::value_initialized<crypto::secret_key>();
}
CRYPTO_MAKE_HASHABLE(public_key)
+CRYPTO_MAKE_HASHABLE(secret_key)
CRYPTO_MAKE_HASHABLE(key_image)
CRYPTO_MAKE_COMPARABLE(signature)
diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp
index dd875402f..fb832d88e 100644
--- a/src/cryptonote_basic/account.cpp
+++ b/src/cryptonote_basic/account.cpp
@@ -131,7 +131,7 @@ DISABLE_VS_WARNINGS(4244 4345)
std::string account_base::get_public_address_str(bool testnet) const
{
//TODO: change this code into base 58
- return get_account_address_as_str(testnet, m_keys.m_account_address);
+ return get_account_address_as_str(testnet, false, m_keys.m_account_address);
}
//-----------------------------------------------------------------
std::string account_base::get_public_integrated_address_str(const crypto::hash8 &payment_id, bool testnet) const
diff --git a/src/cryptonote_basic/cryptonote_basic.h b/src/cryptonote_basic/cryptonote_basic.h
index eb03d33b9..89dda8c3d 100644
--- a/src/cryptonote_basic/cryptonote_basic.h
+++ b/src/cryptonote_basic/cryptonote_basic.h
@@ -411,6 +411,17 @@ namespace cryptonote
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_spend_public_key)
KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_view_public_key)
END_KV_SERIALIZE_MAP()
+
+ bool operator==(const account_public_address& rhs) const
+ {
+ return m_spend_public_key == rhs.m_spend_public_key &&
+ m_view_public_key == rhs.m_view_public_key;
+ }
+
+ bool operator!=(const account_public_address& rhs) const
+ {
+ return !(*this == rhs);
+ }
};
struct keypair
@@ -429,6 +440,21 @@ namespace cryptonote
}
+namespace std {
+ template <>
+ struct hash<cryptonote::account_public_address>
+ {
+ std::size_t operator()(const cryptonote::account_public_address& addr) const
+ {
+ // https://stackoverflow.com/a/17017281
+ size_t res = 17;
+ res = res * 31 + hash<crypto::public_key>()(addr.m_spend_public_key);
+ res = res * 31 + hash<crypto::public_key>()(addr.m_view_public_key);
+ return res;
+ }
+ };
+}
+
BLOB_SERIALIZER(cryptonote::txout_to_key);
BLOB_SERIALIZER(cryptonote::txout_to_scripthash);
diff --git a/src/cryptonote_basic/cryptonote_basic_impl.cpp b/src/cryptonote_basic/cryptonote_basic_impl.cpp
index a59f96956..1183fda06 100644
--- a/src/cryptonote_basic/cryptonote_basic_impl.cpp
+++ b/src/cryptonote_basic/cryptonote_basic_impl.cpp
@@ -158,11 +158,13 @@ namespace cryptonote {
//-----------------------------------------------------------------------
std::string get_account_address_as_str(
bool testnet
+ , bool subaddress
, account_public_address const & adr
)
{
uint64_t address_prefix = testnet ?
- config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
+ (subaddress ? config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX) :
+ (subaddress ? config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX);
return tools::base58::encode_addr(address_prefix, t_serializable_object_to_blob(adr));
}
@@ -173,8 +175,7 @@ namespace cryptonote {
, crypto::hash8 const & payment_id
)
{
- uint64_t integrated_address_prefix = testnet ?
- config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
+ uint64_t integrated_address_prefix = testnet ? config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
integrated_address iadr = {
adr, payment_id
@@ -193,10 +194,8 @@ namespace cryptonote {
return true;
}
//-----------------------------------------------------------------------
- bool get_account_integrated_address_from_str(
- account_public_address& adr
- , bool& has_payment_id
- , crypto::hash8& payment_id
+ bool get_account_address_from_str(
+ address_parse_info& info
, bool testnet
, std::string const & str
)
@@ -205,6 +204,8 @@ namespace cryptonote {
config::testnet::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX;
uint64_t integrated_address_prefix = testnet ?
config::testnet::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX;
+ uint64_t subaddress_prefix = testnet ?
+ config::testnet::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX : config::CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX;
if (2 * sizeof(public_address_outer_blob) != str.size())
{
@@ -218,18 +219,27 @@ namespace cryptonote {
if (integrated_address_prefix == prefix)
{
- has_payment_id = true;
+ info.is_subaddress = false;
+ info.has_payment_id = true;
}
else if (address_prefix == prefix)
{
- has_payment_id = false;
+ info.is_subaddress = false;
+ info.has_payment_id = false;
+ }
+ else if (subaddress_prefix == prefix)
+ {
+ info.is_subaddress = true;
+ info.has_payment_id = false;
}
else {
- LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix << " or " << integrated_address_prefix);
+ LOG_PRINT_L1("Wrong address prefix: " << prefix << ", expected " << address_prefix
+ << " or " << integrated_address_prefix
+ << " or " << subaddress_prefix);
return false;
}
- if (has_payment_id)
+ if (info.has_payment_id)
{
integrated_address iadr;
if (!::serialization::parse_binary(data, iadr))
@@ -237,19 +247,19 @@ namespace cryptonote {
LOG_PRINT_L1("Account public address keys can't be parsed");
return false;
}
- adr = iadr.adr;
- payment_id = iadr.payment_id;
+ info.address = iadr.adr;
+ info.payment_id = iadr.payment_id;
}
else
{
- if (!::serialization::parse_binary(data, adr))
+ if (!::serialization::parse_binary(data, info.address))
{
LOG_PRINT_L1("Account public address keys can't be parsed");
return false;
}
}
- if (!crypto::check_key(adr.m_spend_public_key) || !crypto::check_key(adr.m_view_public_key))
+ if (!crypto::check_key(info.address.m_spend_public_key) || !crypto::check_key(info.address.m_view_public_key))
{
LOG_PRINT_L1("Failed to validate address keys");
return false;
@@ -284,51 +294,27 @@ namespace cryptonote {
}
//we success
- adr = blob.m_address;
- has_payment_id = false;
+ info.address = blob.m_address;
+ info.is_subaddress = false;
+ info.has_payment_id = false;
}
return true;
}
- //-----------------------------------------------------------------------
- bool get_account_address_from_str(
- account_public_address& adr
- , bool testnet
- , std::string const & str
- )
- {
- bool has_payment_id;
- crypto::hash8 payment_id;
- return get_account_integrated_address_from_str(adr, has_payment_id, payment_id, testnet, str);
- }
//--------------------------------------------------------------------------------
bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
- , bool& has_payment_id
- , crypto::hash8& payment_id
+ address_parse_info& info
, bool testnet
, const std::string& str_or_url
, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm
)
{
- if (get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, str_or_url))
+ if (get_account_address_from_str(info, testnet, str_or_url))
return true;
bool dnssec_valid;
std::string address_str = tools::dns_utils::get_account_address_as_str_from_url(str_or_url, dnssec_valid, dns_confirm);
return !address_str.empty() &&
- get_account_integrated_address_from_str(address, has_payment_id, payment_id, testnet, address_str);
- }
- //--------------------------------------------------------------------------------
- bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
- , bool testnet
- , const std::string& str_or_url
- , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm
- )
- {
- bool has_payment_id;
- crypto::hash8 payment_id;
- return get_account_address_from_str_or_url(address, has_payment_id, payment_id, testnet, str_or_url, dns_confirm);
+ get_account_address_from_str(info, testnet, address_str);
}
//--------------------------------------------------------------------------------
bool operator ==(const cryptonote::transaction& a, const cryptonote::transaction& b) {
diff --git a/src/cryptonote_basic/cryptonote_basic_impl.h b/src/cryptonote_basic/cryptonote_basic_impl.h
index 5523846d6..08d966fed 100644
--- a/src/cryptonote_basic/cryptonote_basic_impl.h
+++ b/src/cryptonote_basic/cryptonote_basic_impl.h
@@ -75,6 +75,14 @@ namespace cryptonote {
}
}
+ struct address_parse_info
+ {
+ account_public_address address;
+ bool is_subaddress;
+ bool has_payment_id;
+ crypto::hash8 payment_id;
+ };
+
/************************************************************************/
/* Cryptonote helper functions */
/************************************************************************/
@@ -87,6 +95,7 @@ namespace cryptonote {
std::string get_account_address_as_str(
bool testnet
+ , bool subaddress
, const account_public_address& adr
);
@@ -96,31 +105,14 @@ namespace cryptonote {
, const crypto::hash8& payment_id
);
- bool get_account_integrated_address_from_str(
- account_public_address& adr
- , bool& has_payment_id
- , crypto::hash8& payment_id
- , bool testnet
- , const std::string& str
- );
-
bool get_account_address_from_str(
- account_public_address& adr
+ address_parse_info& info
, bool testnet
, const std::string& str
);
bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
- , bool& has_payment_id
- , crypto::hash8& payment_id
- , bool testnet
- , const std::string& str_or_url
- , std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address
- );
-
- bool get_account_address_from_str_or_url(
- cryptonote::account_public_address& address
+ address_parse_info& info
, bool testnet
, const std::string& str_or_url
, std::function<std::string(const std::string&, const std::vector<std::string>&, bool)> dns_confirm = return_first_address
diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h
index 6e4ac9b72..a67fa0ae7 100644
--- a/src/cryptonote_basic/cryptonote_boost_serialization.h
+++ b/src/cryptonote_basic/cryptonote_boost_serialization.h
@@ -83,6 +83,11 @@ namespace boost
{
a & reinterpret_cast<char (&)[sizeof(crypto::hash)]>(x);
}
+ template <class Archive>
+ inline void serialize(Archive &a, crypto::hash8 &x, const boost::serialization::version_type ver)
+ {
+ a & reinterpret_cast<char (&)[sizeof(crypto::hash8)]>(x);
+ }
template <class Archive>
inline void serialize(Archive &a, cryptonote::txout_to_script &x, const boost::serialization::version_type ver)
diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp
index fc979f288..6f171f227 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.cpp
+++ b/src/cryptonote_basic/cryptonote_format_utils.cpp
@@ -129,16 +129,69 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki)
+ crypto::secret_key get_subaddress_secret_key(const crypto::secret_key& a, const subaddress_index& index)
+ {
+ const char prefix[] = "SubAddr";
+ char data[sizeof(prefix) + sizeof(crypto::secret_key) + sizeof(subaddress_index)];
+ memcpy(data, prefix, sizeof(prefix));
+ memcpy(data + sizeof(prefix), &a, sizeof(crypto::secret_key));
+ memcpy(data + sizeof(prefix) + sizeof(crypto::secret_key), &index, sizeof(subaddress_index));
+ crypto::secret_key m;
+ crypto::hash_to_scalar(data, sizeof(data), m);
+ return m;
+ }
+ //---------------------------------------------------------------
+ bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki)
{
crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation);
bool r = crypto::generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation);
CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")");
- r = crypto::derive_public_key(recv_derivation, real_output_index, ack.m_account_address.m_spend_public_key, in_ephemeral.pub);
- CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to derive_public_key(" << recv_derivation << ", " << real_output_index << ", " << ack.m_account_address.m_spend_public_key << ")");
+ std::vector<crypto::key_derivation> additional_recv_derivations;
+ for (size_t i = 0; i < additional_tx_public_keys.size(); ++i)
+ {
+ crypto::key_derivation additional_recv_derivation = AUTO_VAL_INIT(additional_recv_derivation);
+ r = crypto::generate_key_derivation(additional_tx_public_keys[i], ack.m_view_secret_key, additional_recv_derivation);
+ CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")");
+ additional_recv_derivations.push_back(additional_recv_derivation);
+ }
+
+ boost::optional<subaddress_receive_info> subaddr_recv_info = is_out_to_acc_precomp(subaddresses, out_key, recv_derivation, additional_recv_derivations, real_output_index);
+ CHECK_AND_ASSERT_MES(subaddr_recv_info, false, "key image helper: given output pubkey doesn't seem to belong to this address");
+
+ return generate_key_image_helper_precomp(ack, out_key, subaddr_recv_info->derivation, real_output_index, subaddr_recv_info->index, in_ephemeral, ki);
+ }
+ //---------------------------------------------------------------
+ bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki)
+ {
+ if (ack.m_spend_secret_key == crypto::null_skey)
+ {
+ // for watch-only wallet, simply copy the known output pubkey
+ in_ephemeral.pub = out_key;
+ in_ephemeral.sec = crypto::null_skey;
+ }
+ else
+ {
+ // derive secret key with subaddress - step 1: original CN derivation
+ crypto::secret_key scalar_step1;
+ crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, scalar_step1); // computes Hs(a*R || idx) + b
+
+ // step 2: add Hs(a || index_major || index_minor)
+ crypto::secret_key scalar_step2;
+ if (received_index.is_zero())
+ {
+ scalar_step2 = scalar_step1; // treat index=(0,0) as a special case representing the main address
+ }
+ else
+ {
+ crypto::secret_key m = get_subaddress_secret_key(ack.m_view_secret_key, received_index);
+ sc_add((unsigned char*)&scalar_step2, (unsigned char*)&scalar_step1, (unsigned char*)&m);
+ }
- crypto::derive_secret_key(recv_derivation, real_output_index, ack.m_spend_secret_key, in_ephemeral.sec);
+ in_ephemeral.sec = scalar_step2;
+ crypto::secret_key_to_public_key(in_ephemeral.sec, in_ephemeral.pub);
+ CHECK_AND_ASSERT_MES(in_ephemeral.pub == out_key, false, "key image helper precomp: given output pubkey doesn't match the derived one");
+ }
crypto::generate_key_image(in_ephemeral.pub, in_ephemeral.sec, ki);
return true;
@@ -277,6 +330,40 @@ namespace cryptonote
return true;
}
//---------------------------------------------------------------
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra)
+ {
+ // parse
+ std::vector<tx_extra_field> tx_extra_fields;
+ parse_tx_extra(tx_extra, tx_extra_fields);
+ // find corresponding field
+ tx_extra_additional_pub_keys additional_pub_keys;
+ if(!find_tx_extra_field_by_type(tx_extra_fields, additional_pub_keys))
+ return {};
+ return additional_pub_keys.data;
+ }
+ //---------------------------------------------------------------
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx)
+ {
+ return get_additional_tx_pub_keys_from_extra(tx.extra);
+ }
+ //---------------------------------------------------------------
+ bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys)
+ {
+ // convert to variant
+ tx_extra_field field = tx_extra_additional_pub_keys{ additional_pub_keys };
+ // serialize
+ std::ostringstream oss;
+ binary_archive<true> ar(oss);
+ bool r = ::do_serialize(ar, field);
+ CHECK_AND_NO_ASSERT_MES_L1(r, false, "failed to serialize tx extra additional tx pub keys");
+ // append
+ std::string tx_extra_str = oss.str();
+ size_t pos = tx_extra.size();
+ tx_extra.resize(tx_extra.size() + tx_extra_str.size());
+ memcpy(&tx_extra[pos], tx_extra_str.data(), tx_extra_str.size());
+ return true;
+ }
+ //---------------------------------------------------------------
bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce)
{
CHECK_AND_ASSERT_MES(extra_nonce.size() <= TX_EXTRA_NONCE_MAX_COUNT, false, "extra nonce could be 255 bytes max");
@@ -480,20 +567,43 @@ namespace cryptonote
return res;
}
//---------------------------------------------------------------
- bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index)
+ bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, size_t output_index)
{
crypto::key_derivation derivation;
generate_key_derivation(tx_pub_key, acc.m_view_secret_key, derivation);
crypto::public_key pk;
derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
- return pk == out_key.key;
+ if (pk == out_key.key)
+ return true;
+ // try additional tx pubkeys if available
+ if (!additional_tx_pub_keys.empty())
+ {
+ CHECK_AND_ASSERT_MES(output_index < additional_tx_pub_keys.size(), false, "wrong number of additional tx pubkeys");
+ generate_key_derivation(additional_tx_pub_keys[output_index], acc.m_view_secret_key, derivation);
+ derive_public_key(derivation, output_index, acc.m_account_address.m_spend_public_key, pk);
+ return pk == out_key.key;
+ }
+ return false;
}
//---------------------------------------------------------------
- bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index)
+ boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index)
{
- crypto::public_key pk;
- derive_public_key(derivation, output_index, spend_public_key, pk);
- return pk == out_key.key;
+ // try the shared tx pubkey
+ crypto::public_key subaddress_spendkey;
+ derive_subaddress_public_key(out_key, derivation, output_index, subaddress_spendkey);
+ auto found = subaddresses.find(subaddress_spendkey);
+ if (found != subaddresses.end())
+ return subaddress_receive_info{ found->second, derivation };
+ // try additional tx pubkeys if available
+ if (!additional_derivations.empty())
+ {
+ CHECK_AND_ASSERT_MES(output_index < additional_derivations.size(), boost::none, "wrong number of additional derivations");
+ derive_subaddress_public_key(out_key, additional_derivations[output_index], output_index, subaddress_spendkey);
+ found = subaddresses.find(subaddress_spendkey);
+ if (found != subaddresses.end())
+ return subaddress_receive_info{ found->second, additional_derivations[output_index] };
+ }
+ return boost::none;
}
//---------------------------------------------------------------
bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered)
@@ -501,17 +611,19 @@ namespace cryptonote
crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx);
if(null_pkey == tx_pub_key)
return false;
- return lookup_acc_outs(acc, tx, tx_pub_key, outs, money_transfered);
+ std::vector<crypto::public_key> additional_tx_pub_keys = get_additional_tx_pub_keys_from_extra(tx);
+ return lookup_acc_outs(acc, tx, tx_pub_key, additional_tx_pub_keys, outs, money_transfered);
}
//---------------------------------------------------------------
- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered)
+ bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_pub_keys, std::vector<size_t>& outs, uint64_t& money_transfered)
{
+ CHECK_AND_ASSERT_MES(additional_tx_pub_keys.empty() || additional_tx_pub_keys.size() == tx.vout.size(), false, "wrong number of additional pubkeys" );
money_transfered = 0;
size_t i = 0;
for(const tx_out& o: tx.vout)
{
CHECK_AND_ASSERT_MES(o.target.type() == typeid(txout_to_key), false, "wrong type id in transaction out" );
- if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, i))
+ if(is_out_to_acc(acc, boost::get<txout_to_key>(o.target), tx_pub_key, additional_tx_pub_keys, i))
{
outs.push_back(i);
money_transfered += o.amount;
diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h
index 00080fb98..078c8b8a0 100644
--- a/src/cryptonote_basic/cryptonote_format_utils.h
+++ b/src/cryptonote_basic/cryptonote_format_utils.h
@@ -32,9 +32,11 @@
#include "cryptonote_protocol/cryptonote_protocol_defs.h"
#include "cryptonote_basic_impl.h"
#include "account.h"
+#include "subaddress_index.h"
#include "include_base_utils.h"
#include "crypto/crypto.h"
#include "crypto/hash.h"
+#include <unordered_map>
namespace cryptonote
{
@@ -63,19 +65,29 @@ namespace cryptonote
crypto::public_key get_tx_pub_key_from_extra(const transaction_prefix& tx, size_t pk_index = 0);
crypto::public_key get_tx_pub_key_from_extra(const transaction& tx, size_t pk_index = 0);
bool add_tx_pub_key_to_extra(transaction& tx, const crypto::public_key& tx_pub_key);
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const std::vector<uint8_t>& tx_extra);
+ std::vector<crypto::public_key> get_additional_tx_pub_keys_from_extra(const transaction_prefix& tx);
+ bool add_additional_tx_pub_keys_to_extra(std::vector<uint8_t>& tx_extra, const std::vector<crypto::public_key>& additional_pub_keys);
bool add_extra_nonce_to_tx_extra(std::vector<uint8_t>& tx_extra, const blobdata& extra_nonce);
bool remove_field_from_tx_extra(std::vector<uint8_t>& tx_extra, const std::type_info &type);
void set_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash& payment_id);
void set_encrypted_payment_id_to_tx_extra_nonce(blobdata& extra_nonce, const crypto::hash8& payment_id);
bool get_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash& payment_id);
bool get_encrypted_payment_id_from_tx_extra_nonce(const blobdata& extra_nonce, crypto::hash8& payment_id);
- bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, size_t output_index);
- bool is_out_to_acc_precomp(const crypto::public_key& spend_public_key, const txout_to_key& out_key, const crypto::key_derivation& derivation, size_t output_index);
- bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, std::vector<size_t>& outs, uint64_t& money_transfered);
+ bool is_out_to_acc(const account_keys& acc, const txout_to_key& out_key, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t output_index);
+ struct subaddress_receive_info
+ {
+ subaddress_index index;
+ crypto::key_derivation derivation;
+ };
+ boost::optional<subaddress_receive_info> is_out_to_acc_precomp(const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::key_derivation& derivation, const std::vector<crypto::key_derivation>& additional_derivations, size_t output_index);
+ bool lookup_acc_outs(const account_keys& acc, const transaction& tx, const crypto::public_key& tx_pub_key, const std::vector<crypto::public_key>& additional_tx_public_keys, std::vector<size_t>& outs, uint64_t& money_transfered);
bool lookup_acc_outs(const account_keys& acc, const transaction& tx, std::vector<size_t>& outs, uint64_t& money_transfered);
bool get_tx_fee(const transaction& tx, uint64_t & fee);
uint64_t get_tx_fee(const transaction& tx);
- bool generate_key_image_helper(const account_keys& ack, const crypto::public_key& tx_public_key, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki);
+ crypto::secret_key get_subaddress_secret_key(const crypto::secret_key& a, const subaddress_index& index);
+ bool generate_key_image_helper(const account_keys& ack, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, const crypto::public_key& out_key, const crypto::public_key& tx_public_key, const std::vector<crypto::public_key>& additional_tx_public_keys, size_t real_output_index, keypair& in_ephemeral, crypto::key_image& ki);
+ bool generate_key_image_helper_precomp(const account_keys& ack, const crypto::public_key& out_key, const crypto::key_derivation& recv_derivation, size_t real_output_index, const subaddress_index& received_index, keypair& in_ephemeral, crypto::key_image& ki);
void get_blob_hash(const blobdata& blob, crypto::hash& res);
crypto::hash get_blob_hash(const blobdata& blob);
std::string short_hash_str(const crypto::hash& h);
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index b620e3426..8f53c2d3c 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -228,11 +228,13 @@ namespace cryptonote
if(command_line::has_arg(vm, arg_start_mining))
{
- if(!cryptonote::get_account_address_from_str(m_mine_address, testnet, command_line::get_arg(vm, arg_start_mining)))
+ address_parse_info info;
+ if(!cryptonote::get_account_address_from_str(info, testnet, command_line::get_arg(vm, arg_start_mining)) || info.is_subaddress)
{
LOG_ERROR("Target account address " << command_line::get_arg(vm, arg_start_mining) << " has wrong format, starting daemon canceled");
return false;
}
+ m_mine_address = info.address;
m_threads_total = 1;
m_do_mining = true;
if(command_line::has_arg(vm, arg_mining_threads))
diff --git a/src/cryptonote_basic/subaddress_index.h b/src/cryptonote_basic/subaddress_index.h
new file mode 100644
index 000000000..07d13c503
--- /dev/null
+++ b/src/cryptonote_basic/subaddress_index.h
@@ -0,0 +1,102 @@
+// Copyright (c) 2017, 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.
+
+#pragma once
+
+#include "serialization/keyvalue_serialization.h"
+#include <boost/serialization/serialization.hpp>
+#include <boost/serialization/version.hpp>
+#include <ostream>
+
+namespace cryptonote
+{
+ struct subaddress_index
+ {
+ uint32_t major;
+ uint32_t minor;
+ bool operator==(const subaddress_index& rhs) const { return !memcmp(this, &rhs, sizeof(subaddress_index)); }
+ bool operator!=(const subaddress_index& rhs) const { return !(*this == rhs); }
+ bool is_zero() const { return major == 0 && minor == 0; }
+
+ BEGIN_SERIALIZE_OBJECT()
+ FIELD(major)
+ FIELD(minor)
+ END_SERIALIZE()
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(major)
+ KV_SERIALIZE(minor)
+ END_KV_SERIALIZE_MAP()
+ };
+}
+
+namespace cryptonote {
+ inline std::ostream& operator<<(std::ostream& out, const cryptonote::subaddress_index& subaddr_index)
+ {
+ return out << subaddr_index.major << '/' << subaddr_index.minor;
+ }
+}
+
+namespace std
+{
+ template <>
+ struct hash<cryptonote::subaddress_index>
+ {
+ size_t operator()(const cryptonote::subaddress_index& index ) const
+ {
+ size_t res;
+ if (sizeof(size_t) == 8)
+ {
+ res = ((uint64_t)index.major << 32) | index.minor;
+ }
+ else
+ {
+ // https://stackoverflow.com/a/17017281
+ res = 17;
+ res = res * 31 + hash<uint32_t>()(index.major);
+ res = res * 31 + hash<uint32_t>()(index.minor);
+ }
+ return res;
+ }
+ };
+}
+
+BOOST_CLASS_VERSION(cryptonote::subaddress_index, 0)
+
+namespace boost
+{
+ namespace serialization
+ {
+ template <class Archive>
+ inline void serialize(Archive &a, cryptonote::subaddress_index &x, const boost::serialization::version_type ver)
+ {
+ a & x.major;
+ a & x.minor;
+ }
+ }
+}
diff --git a/src/cryptonote_basic/tx_extra.h b/src/cryptonote_basic/tx_extra.h
index 5a6c3176d..e12828a9f 100644
--- a/src/cryptonote_basic/tx_extra.h
+++ b/src/cryptonote_basic/tx_extra.h
@@ -38,6 +38,7 @@
#define TX_EXTRA_TAG_PUBKEY 0x01
#define TX_EXTRA_NONCE 0x02
#define TX_EXTRA_MERGE_MINING_TAG 0x03
+#define TX_EXTRA_TAG_ADDITIONAL_PUBKEYS 0x04
#define TX_EXTRA_MYSTERIOUS_MINERGATE_TAG 0xDE
#define TX_EXTRA_NONCE_PAYMENT_ID 0x00
@@ -159,6 +160,16 @@ namespace cryptonote
}
};
+ // per-output additional tx pubkey for multi-destination transfers involving at least one subaddress
+ struct tx_extra_additional_pub_keys
+ {
+ std::vector<crypto::public_key> data;
+
+ BEGIN_SERIALIZE()
+ FIELD(data)
+ END_SERIALIZE()
+ };
+
struct tx_extra_mysterious_minergate
{
std::string data;
@@ -172,11 +183,12 @@ namespace cryptonote
// varint tag;
// varint size;
// varint data[];
- typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_mysterious_minergate> tx_extra_field;
+ typedef boost::variant<tx_extra_padding, tx_extra_pub_key, tx_extra_nonce, tx_extra_merge_mining_tag, tx_extra_additional_pub_keys, tx_extra_mysterious_minergate> tx_extra_field;
}
VARIANT_TAG(binary_archive, cryptonote::tx_extra_padding, TX_EXTRA_TAG_PADDING);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_pub_key, TX_EXTRA_TAG_PUBKEY);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_nonce, TX_EXTRA_NONCE);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_merge_mining_tag, TX_EXTRA_MERGE_MINING_TAG);
+VARIANT_TAG(binary_archive, cryptonote::tx_extra_additional_pub_keys, TX_EXTRA_TAG_ADDITIONAL_PUBKEYS);
VARIANT_TAG(binary_archive, cryptonote::tx_extra_mysterious_minergate, TX_EXTRA_MYSTERIOUS_MINERGATE_TAG);
diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h
index a143c307f..d6973a3fb 100644
--- a/src/cryptonote_config.h
+++ b/src/cryptonote_config.h
@@ -147,6 +147,7 @@ namespace config
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 18;
uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 19;
+ uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 26;
uint16_t const P2P_DEFAULT_PORT = 18080;
uint16_t const RPC_DEFAULT_PORT = 18081;
uint16_t const ZMQ_RPC_DEFAULT_PORT = 18082;
@@ -160,6 +161,7 @@ namespace config
{
uint64_t const CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = 53;
uint64_t const CRYPTONOTE_PUBLIC_INTEGRATED_ADDRESS_BASE58_PREFIX = 54;
+ uint64_t const CRYPTONOTE_PUBLIC_SUBADDRESS_BASE58_PREFIX = 63;
uint16_t const P2P_DEFAULT_PORT = 28080;
uint16_t const RPC_DEFAULT_PORT = 28081;
uint16_t const ZMQ_RPC_DEFAULT_PORT = 28082;
diff --git a/src/cryptonote_core/cryptonote_tx_utils.cpp b/src/cryptonote_core/cryptonote_tx_utils.cpp
index fd647a356..586df9079 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.cpp
+++ b/src/cryptonote_core/cryptonote_tx_utils.cpp
@@ -28,6 +28,7 @@
//
// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+#include <unordered_set>
#include "include_base_utils.h"
using namespace epee;
@@ -157,8 +158,14 @@ namespace cryptonote
return destinations[0].addr.m_view_public_key;
}
//---------------------------------------------------------------
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct)
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const cryptonote::account_public_address& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct)
{
+ if (destinations.empty())
+ {
+ LOG_ERROR("The destinations must be non-empty");
+ return false;
+ }
+
std::vector<rct::key> amount_keys;
tx.set_null();
amount_keys.clear();
@@ -237,8 +244,12 @@ namespace cryptonote
in_contexts.push_back(input_generation_context_data());
keypair& in_ephemeral = in_contexts.back().in_ephemeral;
crypto::key_image img;
- if(!generate_key_image_helper(sender_account_keys, src_entr.real_out_tx_key, src_entr.real_output_in_tx_index, in_ephemeral, img))
+ const auto& out_key = reinterpret_cast<const crypto::public_key&>(src_entr.outputs[src_entr.real_output].second.dest);
+ if(!generate_key_image_helper(sender_account_keys, subaddresses, out_key, src_entr.real_out_tx_key, src_entr.real_out_additional_tx_keys, src_entr.real_output_in_tx_index, in_ephemeral, img))
+ {
+ LOG_ERROR("Key image generation failed!");
return false;
+ }
//check that derivated key is equal with real output key
if( !(in_ephemeral.pub == src_entr.outputs[src_entr.real_output].second.dest) )
@@ -283,6 +294,47 @@ namespace cryptonote
std::swap(sources[i0], sources[i1]);
});
+ // figure out if we need to make additional tx pubkeys
+ size_t num_stdaddresses = 0;
+ size_t num_subaddresses = 0;
+ std::unordered_set<cryptonote::account_public_address> unique_dst_addresses;
+ account_public_address single_dest_subaddress;
+ for(const tx_destination_entry& dst_entr: destinations)
+ {
+ if (dst_entr.addr == change_addr)
+ continue;
+ if (unique_dst_addresses.count(dst_entr.addr) == 0)
+ {
+ unique_dst_addresses.insert(dst_entr.addr);
+ if (dst_entr.is_subaddress)
+ {
+ ++num_subaddresses;
+ single_dest_subaddress = dst_entr.addr;
+ }
+ else
+ {
+ ++num_stdaddresses;
+ }
+ }
+ }
+ LOG_PRINT_L2("destinations include " << num_stdaddresses << " standard addresses and " << num_subaddresses << "subaddresses");
+
+ // if this is a single-destination transfer to a subaddress, we set the tx pubkey to R=s*D
+ if (num_stdaddresses == 0 && num_subaddresses == 1)
+ {
+ txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(single_dest_subaddress.m_spend_public_key), rct::sk2rct(txkey.sec)));
+ remove_field_from_tx_extra(tx.extra, typeid(tx_extra_pub_key));
+ add_tx_pub_key_to_extra(tx, txkey.pub);
+ }
+
+ std::vector<crypto::public_key> additional_tx_public_keys;
+ additional_tx_keys.clear();
+
+ // we don't need to include additional tx keys if:
+ // - all the destinations are standard addresses
+ // - there's only one destination which is a subaddress
+ bool need_additional_txkeys = num_subaddresses > 0 && (num_stdaddresses > 0 || num_subaddresses > 1);
+
uint64_t summary_outs_money = 0;
//fill outputs
size_t output_index = 0;
@@ -291,8 +343,35 @@ namespace cryptonote
CHECK_AND_ASSERT_MES(dst_entr.amount > 0 || tx.version > 1, false, "Destination with wrong amount: " << dst_entr.amount);
crypto::key_derivation derivation;
crypto::public_key out_eph_public_key;
- bool r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, txkey.sec, derivation);
- CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << txkey.sec << ")");
+
+ // make additional tx pubkey if necessary
+ keypair additional_txkey;
+ if (need_additional_txkeys)
+ {
+ additional_txkey = keypair::generate();
+ if (dst_entr.is_subaddress)
+ additional_txkey.pub = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(dst_entr.addr.m_spend_public_key), rct::sk2rct(additional_txkey.sec)));
+ }
+
+ bool r;
+ if (dst_entr.addr == change_addr)
+ {
+ // sending change to yourself; derivation = a*R
+ r = crypto::generate_key_derivation(txkey.pub, sender_account_keys.m_view_secret_key, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << txkey.pub << ", " << sender_account_keys.m_view_secret_key << ")");
+ }
+ else
+ {
+ // sending to the recipient; derivation = r*A (or s*C in the subaddress scheme)
+ r = crypto::generate_key_derivation(dst_entr.addr.m_view_public_key, dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec, derivation);
+ CHECK_AND_ASSERT_MES(r, false, "at creation outs: failed to generate_key_derivation(" << dst_entr.addr.m_view_public_key << ", " << (dst_entr.is_subaddress && need_additional_txkeys ? additional_txkey.sec : txkey.sec) << ")");
+ }
+
+ if (need_additional_txkeys)
+ {
+ additional_tx_public_keys.push_back(additional_txkey.pub);
+ additional_tx_keys.push_back(additional_txkey.sec);
+ }
if (tx.version > 1)
{
@@ -313,6 +392,17 @@ namespace cryptonote
summary_outs_money += dst_entr.amount;
}
+ remove_field_from_tx_extra(tx.extra, typeid(tx_extra_additional_pub_keys));
+ add_additional_tx_pub_keys_to_extra(tx.extra, additional_tx_public_keys);
+
+ LOG_PRINT_L2("tx pubkey: " << txkey.pub);
+ if (need_additional_txkeys)
+ {
+ LOG_PRINT_L2("additional tx pubkeys: ");
+ for (size_t i = 0; i < additional_tx_public_keys.size(); ++i)
+ LOG_PRINT_L2(additional_tx_public_keys[i]);
+ }
+
//check money
if(summary_outs_money > summary_inputs_money )
{
@@ -477,8 +567,11 @@ namespace cryptonote
//---------------------------------------------------------------
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time)
{
+ std::unordered_map<crypto::public_key, cryptonote::subaddress_index> subaddresses;
+ subaddresses[sender_account_keys.m_account_address.m_spend_public_key] = {0,0};
crypto::secret_key tx_key;
- return construct_tx_and_get_tx_key(sender_account_keys, sources, destinations, extra, tx, unlock_time, tx_key);
+ std::vector<crypto::secret_key> additional_tx_keys;
+ return construct_tx_and_get_tx_key(sender_account_keys, subaddresses, sources, destinations, destinations.back().addr, extra, tx, unlock_time, tx_key, additional_tx_keys);
}
//---------------------------------------------------------------
bool generate_genesis_block(
diff --git a/src/cryptonote_core/cryptonote_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index f2cc73545..b5de44b88 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -46,6 +46,7 @@ namespace cryptonote
std::vector<output_entry> outputs; //index + key + optional ringct commitment
size_t real_output; //index in outputs vector of real output_entry
crypto::public_key real_out_tx_key; //incoming real tx public key
+ std::vector<crypto::public_key> real_out_additional_tx_keys; //incoming real tx additional public keys
size_t real_output_in_tx_index; //index in transaction outputs vector
uint64_t amount; //money
bool rct; //true if the output is rct
@@ -58,20 +59,22 @@ namespace cryptonote
{
uint64_t amount; //money
account_public_address addr; //destination address
+ bool is_subaddress;
- tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)) { }
- tx_destination_entry(uint64_t a, const account_public_address &ad) : amount(a), addr(ad) { }
+ tx_destination_entry() : amount(0), addr(AUTO_VAL_INIT(addr)), is_subaddress(false) { }
+ tx_destination_entry(uint64_t a, const account_public_address &ad, bool is_subaddress) : amount(a), addr(ad), is_subaddress(is_subaddress) { }
BEGIN_SERIALIZE_OBJECT()
VARINT_FIELD(amount)
FIELD(addr)
+ FIELD(is_subaddress)
END_SERIALIZE()
};
//---------------------------------------------------------------
crypto::public_key get_destination_view_key_pub(const std::vector<tx_destination_entry> &destinations, const account_keys &sender_keys);
bool construct_tx(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time);
- bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, bool rct = false);
+ bool construct_tx_and_get_tx_key(const account_keys& sender_account_keys, const std::unordered_map<crypto::public_key, subaddress_index>& subaddresses, std::vector<tx_source_entry>& sources, const std::vector<tx_destination_entry>& destinations, const cryptonote::account_public_address& change_addr, std::vector<uint8_t> extra, transaction& tx, uint64_t unlock_time, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys, bool rct = false);
bool generate_genesis_block(
block& bl
@@ -82,6 +85,7 @@ namespace cryptonote
}
BOOST_CLASS_VERSION(cryptonote::tx_source_entry, 0)
+BOOST_CLASS_VERSION(cryptonote::tx_destination_entry, 1)
namespace boost
{
@@ -98,5 +102,15 @@ namespace boost
a & x.rct;
a & x.mask;
}
+
+ template <class Archive>
+ inline void serialize(Archive& a, cryptonote::tx_destination_entry& x, const boost::serialization::version_type ver)
+ {
+ a & x.amount;
+ a & x.addr;
+ if (ver < 1)
+ return;
+ a & x.is_subaddress;
+ }
}
}