diff options
Diffstat (limited to '')
-rw-r--r-- | src/crypto/crypto.cpp | 125 | ||||
-rw-r--r-- | src/crypto/crypto.h | 27 | ||||
-rw-r--r-- | src/cryptonote_basic/account.cpp | 2 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_basic.h | 26 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_basic_impl.cpp | 76 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_basic_impl.h | 30 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_boost_serialization.h | 5 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_format_utils.cpp | 138 | ||||
-rw-r--r-- | src/cryptonote_basic/cryptonote_format_utils.h | 20 | ||||
-rw-r--r-- | src/cryptonote_basic/miner.cpp | 4 | ||||
-rw-r--r-- | src/cryptonote_basic/subaddress_index.h | 102 | ||||
-rw-r--r-- | src/cryptonote_basic/tx_extra.h | 14 | ||||
-rw-r--r-- | src/cryptonote_config.h | 2 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_tx_utils.cpp | 103 | ||||
-rw-r--r-- | src/cryptonote_core/cryptonote_tx_utils.h | 20 |
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; + } } } |