From ca6115300315e9d34c0134933345019672d2f0c0 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 23 Jun 2016 14:38:22 +0300 Subject: [PATCH] Wallet: payment id and integrated address --- src/wallet/api/wallet.cpp | 58 +++++++++++++++++++--- src/wallet/api/wallet.h | 4 +- src/wallet/wallet2_api.h | 17 ++++++- tests/libwallet_api_tests/main.cpp | 79 ++++++++++++++++++++++++++++-- 4 files changed, 144 insertions(+), 14 deletions(-) diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index d72ebc8da..354e27ac6 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -36,6 +36,7 @@ #include "mnemonics/electrum-words.h" #include +#include #include using namespace std; @@ -137,6 +138,13 @@ uint64_t Wallet::amountFromDouble(double amount) return amountFromString(ss.str()); } +std::string Wallet::genPaymentId() +{ + crypto::hash8 payment_id = crypto::rand(); + return epee::string_tools::pod_to_hex(payment_id); + +} + ///////////////////////// WalletImpl implementation //////////////////////// @@ -302,6 +310,15 @@ std::string WalletImpl::address() const return m_wallet->get_account().get_public_address_str(m_wallet->testnet()); } +std::string WalletImpl::integratedAddress(const std::string &payment_id) const +{ + crypto::hash8 pid; + if (!tools::wallet2::parse_short_payment_id(payment_id, pid)) { + pid = crypto::rand(); + } + return m_wallet->get_account().get_public_integrated_address_str(pid, m_wallet->testnet()); +} + bool WalletImpl::store(const std::string &path) { clearStatus(); @@ -380,31 +397,56 @@ bool WalletImpl::refresh() // - payment_details; // - unconfirmed_transfer_details; // - confirmed_transfer_details) -PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, uint64_t amount, uint32_t mixin_count) +PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, uint64_t amount, uint32_t mixin_count) { clearStatus(); vector dsts; cryptonote::tx_destination_entry de; + // indicates if dst_addr is integrated address (address + payment_id) bool has_payment_id; - crypto::hash8 new_payment_id; - + crypto::hash8 payment_id_short; // TODO: (https://bitcointalk.org/index.php?topic=753252.msg9985441#msg9985441) - size_t fake_outs_count = mixin_count > 0 ? mixin_count : m_wallet->default_mixin(); if (fake_outs_count == 0) fake_outs_count = DEFAULT_MIXIN; - PendingTransactionImpl * transaction = new PendingTransactionImpl(this); + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); do { - - if(!cryptonote::get_account_integrated_address_from_str(de.addr, has_payment_id, new_payment_id, m_wallet->testnet(), dst_addr)) { + if(!cryptonote::get_account_integrated_address_from_str(de.addr, has_payment_id, payment_id_short, m_wallet->testnet(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 m_status = Status_Error; m_errorString = "Invalid destination address"; break; } + std::vector extra; + // if dst_addr is not an integrated address, parse payment_id + if (!has_payment_id && !payment_id.empty()) { + // copy-pasted from simplewallet.cpp:2212 + crypto::hash payment_id_long; + bool r = tools::wallet2::parse_long_payment_id(payment_id, payment_id_long); + if (r) { + std::string extra_nonce; + cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_long); + r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + } else { + r = tools::wallet2::parse_short_payment_id(payment_id, payment_id_short); + if (r) { + std::string extra_nonce; + set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, payment_id_short); + r = add_extra_nonce_to_tx_extra(extra, extra_nonce); + } + } + + if (!r) { + m_status = Status_Error; + m_errorString = tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id; + break; + } + } + + de.amount = amount; if (de.amount <= 0) { m_status = Status_Error; @@ -414,7 +456,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, uint64 dsts.push_back(de); //std::vector ptx_vector; - std::vector extra; + try { diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 016116e96..164aede1a 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -60,6 +60,7 @@ public: std::string errorString() const; bool setPassword(const std::string &password); std::string address() const; + std::string integratedAddress(const std::string &payment_id) const; bool store(const std::string &path); std::string filename() const; std::string keysFilename() const; @@ -70,7 +71,8 @@ public: uint64_t balance() const; uint64_t unlockedBalance() const; bool refresh(); - PendingTransaction * createTransaction(const std::string &dst_addr, uint64_t amount, uint32_t mixin_count); + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, + uint64_t amount, uint32_t mixin_count); virtual void disposeTransaction(PendingTransaction * t); virtual TransactionHistory * history() const; virtual void setListener(WalletListener * l); diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h index de0b50591..c0c3d436a 100644 --- a/src/wallet/wallet2_api.h +++ b/src/wallet/wallet2_api.h @@ -134,6 +134,16 @@ struct Wallet virtual std::string errorString() const = 0; virtual bool setPassword(const std::string &password) = 0; virtual std::string address() const = 0; + /*! + * \brief integratedAddress - returns integrated address for current wallet address and given payment_id. + * if passed "payment_id" param is an empty string or not-valid payment id string + * (16 characters hexadecimal string) - random payment_id will be generated + * + * \param payment_id - 16 characters hexadecimal string or empty string if new random payment id needs to be + * generated + * \return - 106 characters string representing integrated address + */ + virtual std::string integratedAddress(const std::string &payment_id) const = 0; /*! * \brief store - stores wallet to file. * \param path - main filename to store wallet to. additionally stores address file and keys file. @@ -162,19 +172,22 @@ struct Wallet static std::string displayAmount(uint64_t amount); static uint64_t amountFromString(const std::string &amount); static uint64_t amountFromDouble(double amount); + static std::string genPaymentId(); // TODO? // virtual uint64_t unlockedDustBalance() const = 0; virtual bool refresh() = 0; /*! - * \brief createTransaction creates transaction + * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored * \param dst_addr destination address as string + * \param payment_id optional payment_id, can be empty string * \param amount amount * \param mixin_count mixin count. if 0 passed, wallet will use default value * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() * after object returned */ - virtual PendingTransaction * createTransaction(const std::string &dst_addr, uint64_t amount, uint32_t mixin_count) = 0; + virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, + uint64_t amount, uint32_t mixin_count) = 0; virtual void disposeTransaction(PendingTransaction * t) = 0; virtual TransactionHistory * history() const = 0; virtual void setListener(WalletListener *) = 0; diff --git a/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp index ddd6969c6..471427c0c 100644 --- a/tests/libwallet_api_tests/main.cpp +++ b/tests/libwallet_api_tests/main.cpp @@ -75,6 +75,7 @@ const std::string TESTNET_WALLET4_NAME = WALLETS_ROOT_DIR + "wallet_04.bin"; const std::string TESTNET_WALLET5_NAME = WALLETS_ROOT_DIR + "wallet_05.bin"; const std::string TESTNET_WALLET6_NAME = WALLETS_ROOT_DIR + "wallet_06.bin"; + const char * TESTNET_WALLET_PASS = ""; const std::string CURRENT_SRC_WALLET = TESTNET_WALLET1_NAME; @@ -85,6 +86,8 @@ const uint64_t AMOUNT_10XMR = 10000000000000L; const uint64_t AMOUNT_5XMR = 5000000000000L; const uint64_t AMOUNT_1XMR = 1000000000000L; +const std::string PAYMENT_ID_EMPTY = ""; + } @@ -247,7 +250,7 @@ TEST_F(WalletManagerTest, WalletManagerChangesPassword) ASSERT_TRUE(wallet1->setPassword(WALLET_PASS2)); ASSERT_TRUE(wmgr->closeWallet(wallet1)); Bitmonero::Wallet * wallet2 = wmgr->openWallet(WALLET_NAME, WALLET_PASS2); - ASSERT_TRUE(wallet2->status() == Bitmonero::Wallet::Status_Ok);quint64 + ASSERT_TRUE(wallet2->status() == Bitmonero::Wallet::Status_Ok); ASSERT_TRUE(wallet2->seed() == seed1); ASSERT_TRUE(wmgr->closeWallet(wallet2)); Bitmonero::Wallet * wallet3 = wmgr->openWallet(WALLET_NAME, WALLET_PASS); @@ -359,6 +362,24 @@ TEST_F(WalletManagerTest, WalletManagerFindsWallet) } } +TEST_F(WalletManagerTest, WalletGeneratesPaymentId) +{ + std::string payment_id = Bitmonero::Wallet::genPaymentId(); + ASSERT_TRUE(payment_id.length() == 16); +} + + +TEST_F(WalletManagerTest, WalletGeneratesIntegratedAddress) +{ + std::string payment_id = Bitmonero::Wallet::genPaymentId(); + + Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); + std::string integrated_address = wallet1->integratedAddress(payment_id); + ASSERT_TRUE(integrated_address.length() == 106); +} + + + TEST_F(WalletTest1, WalletShowsBalance) { @@ -438,6 +459,8 @@ TEST_F(WalletTest1, WalletTransactionWithMixin) mixins.push_back(7); mixins.push_back(8); mixins.push_back(9); mixins.push_back(10); mixins.push_back(15); mixins.push_back(20); mixins.push_back(25); + std::string payment_id = ""; + Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); @@ -452,7 +475,7 @@ TEST_F(WalletTest1, WalletTransactionWithMixin) for (auto mixin : mixins) { std::cerr << "Transaction mixin count: " << mixin << std::endl; Bitmonero::PendingTransaction * transaction = wallet1->createTransaction( - recepient_address, AMOUNT_5XMR, mixin); + recepient_address, payment_id, AMOUNT_5XMR, mixin); std::cerr << "Transaction status: " << transaction->status() << std::endl; std::cerr << "Transaction fee: " << Bitmonero::Wallet::displayAmount(transaction->fee()) << std::endl; @@ -504,18 +527,68 @@ TEST_F(WalletTest1, WalletTransactionAndHistory) std::string wallet4_addr = Utils::get_wallet_address(CURRENT_DST_WALLET, TESTNET_WALLET_PASS); - Bitmonero::PendingTransaction * tx = wallet_src->createTransaction(wallet4_addr, AMOUNT_10XMR * 5, 0); + Bitmonero::PendingTransaction * tx = wallet_src->createTransaction(wallet4_addr, + PAYMENT_ID_EMPTY, + AMOUNT_10XMR * 5, 0); + ASSERT_TRUE(tx->status() == Bitmonero::PendingTransaction::Status_Ok); + ASSERT_TRUE(tx->commit()); + history = wallet_src->history(); + history->refresh(); + ASSERT_TRUE(count1 != history->count()); + + std::cout << "**** Transactions after transfer (" << history->count() << ")" << std::endl; + for (auto t: history->getAll()) { + ASSERT_TRUE(t != nullptr); + Utils::print_transaction(t); + } +} + +TEST_F(WalletTest1, WalletTransactionWithPaymentId) +{ + + Bitmonero::Wallet * wallet_src = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true); + // make sure testnet daemon is running + ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0)); + ASSERT_TRUE(wallet_src->refresh()); + Bitmonero::TransactionHistory * history = wallet_src->history(); + history->refresh(); + ASSERT_TRUE(history->count() > 0); + size_t count1 = history->count(); + + std::cout << "**** Transactions before transfer (" << count1 << ")" << std::endl; + for (auto t: history->getAll()) { + ASSERT_TRUE(t != nullptr); + Utils::print_transaction(t); + } + + std::string wallet4_addr = Utils::get_wallet_address(CURRENT_DST_WALLET, TESTNET_WALLET_PASS); + + std::string payment_id = Bitmonero::Wallet::genPaymentId(); + ASSERT_TRUE(payment_id.length() == 16); + + + Bitmonero::PendingTransaction * tx = wallet_src->createTransaction(wallet4_addr, + payment_id, + AMOUNT_1XMR, 1); + ASSERT_TRUE(tx->status() == Bitmonero::PendingTransaction::Status_Ok); ASSERT_TRUE(tx->commit()); history = wallet_src->history(); history->refresh(); ASSERT_TRUE(count1 != history->count()); + bool payment_id_in_history = false; + std::cout << "**** Transactions after transfer (" << history->count() << ")" << std::endl; for (auto t: history->getAll()) { ASSERT_TRUE(t != nullptr); Utils::print_transaction(t); + if (t->paymentId() == payment_id) { + payment_id_in_history = true; + } } + + ASSERT_TRUE(payment_id_in_history); } struct MyWalletListener : public Bitmonero::WalletListener