diff options
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | .woodpecker/linux.yaml | 7 | ||||
| -rwxr-xr-x | apply_patches.sh | 25 | ||||
| l---------[-rw-r--r--] | libbridge/src/main/cpp/wallet2_api.h | 1498 | ||||
| m--------- | monero | 0 | ||||
| -rw-r--r-- | patches/0000-polyseed.patch | 1302 | ||||
| -rw-r--r-- | patches/0001-background-sync.patch | 4172 | ||||
| -rw-r--r-- | patches/0002-airgap.patch | 184 | ||||
| -rw-r--r-- | patches/0003-coin-control.patch | 916 | ||||
| -rw-r--r-- | patches/0004-fix-build.patch | 134 |
11 files changed, 6741 insertions, 1501 deletions
@@ -1,4 +1,3 @@ -monero* boost_* libexpat libiconv-* diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..fc6562c --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "monero"] + path = monero + url = https://github.com/monero-project/monero diff --git a/.woodpecker/linux.yaml b/.woodpecker/linux.yaml index dbece0b..2fa8342 100644 --- a/.woodpecker/linux.yaml +++ b/.woodpecker/linux.yaml @@ -3,16 +3,17 @@ clone: image: woodpeckerci/plugin-git settings: partial: false + recursive: true labels: platform: linux/amd64 steps: - - name: clone monero and cache depends + - name: patch monero and cache depends image: git.mrcyjanek.net/mrcyjanek/debian:bookworm commands: - - git fetch - - git clone https://git.mrcyjanek.net/mrcyjanek/monero --recursive --depth=1 --branch=release-v0.18.3.2-legacy + - cd monero && git submodule update --init --force && cd .. + - ./apply_patches.sh - cd monero/contrib/depends - make download - name: x86_64-linux-gnu diff --git a/apply_patches.sh b/apply_patches.sh new file mode 100755 index 0000000..78bad34 --- /dev/null +++ b/apply_patches.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [[ ! -d 'monero' ]] +then + echo "no 'monero' directory found. clone with --recursive or run:" + echo "$ git submodule init && git submodule update --force"; + exit 1 +fi + +if [[ -f "monero/.patch-applied" ]]; +then + echo "monero/.patch-applied file exist. manual investigation recommended." + exit 0 +fi + +cd monero +git apply ../patches/* --index +git submodule init +git submodule update --force +touch .patch-applied +git add . +git config user.email "you@example.com" +git config user.name "Your Name" +git commit -m 'patch applied' # fatal: path 'external/polyseed' exists on disk, but not in 'HEAD' +echo "you are good to go!"
\ No newline at end of file diff --git a/libbridge/src/main/cpp/wallet2_api.h b/libbridge/src/main/cpp/wallet2_api.h index 5612c66..6d9c48d 100644..120000 --- a/libbridge/src/main/cpp/wallet2_api.h +++ b/libbridge/src/main/cpp/wallet2_api.h @@ -1,1497 +1 @@ -// Copyright (c) 2014-2022, 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. -// -// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers - -#pragma once - - -#include <string> -#include <vector> -#include <list> -#include <set> -#include <ctime> -#include <iostream> -#include <stdexcept> -#include <cstdint> - -// Public interface for libwallet library -namespace Monero { - -enum NetworkType : uint8_t { - MAINNET = 0, - TESTNET, - STAGENET -}; - - namespace Utils { - bool isAddressLocal(const std::string &hostaddr); - void onStartup(); - } - - template<typename T> - class optional { - public: - optional(): set(false) {} - optional(const T &t): t(t), set(true) {} - const T &operator*() const { return t; } - T &operator*() { return t; } - operator bool() const { return set; } - private: - T t; - bool set; - }; - -/** - * @brief Transaction-like interface for sending money - */ -struct PendingTransaction -{ - enum Status { - Status_Ok, - Status_Error, - Status_Critical - }; - - enum Priority { - Priority_Default = 0, - Priority_Low = 1, - Priority_Medium = 2, - Priority_High = 3, - Priority_Last - }; - - virtual ~PendingTransaction() = 0; - virtual int status() const = 0; - virtual std::string errorString() const = 0; - // commit transaction or save to file if filename is provided. - virtual bool commit(const std::string &filename = "", bool overwrite = false) = 0; - virtual uint64_t amount() const = 0; - virtual uint64_t dust() const = 0; - virtual uint64_t fee() const = 0; - virtual std::vector<std::string> txid() const = 0; - /*! - * \brief txCount - number of transactions current transaction will be splitted to - * \return - */ - virtual uint64_t txCount() const = 0; - virtual std::vector<uint32_t> subaddrAccount() const = 0; - virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0; - - /** - * @brief multisigSignData - * @return encoded multisig transaction with signers' keys. - * Transfer this data to another wallet participant to sign it. - * Assumed use case is: - * 1. Initiator: - * auto data = pendingTransaction->multisigSignData(); - * 2. Signer1: - * pendingTransaction = wallet->restoreMultisigTransaction(data); - * pendingTransaction->signMultisigTx(); - * auto signed = pendingTransaction->multisigSignData(); - * 3. Signer2: - * pendingTransaction = wallet->restoreMultisigTransaction(signed); - * pendingTransaction->signMultisigTx(); - * pendingTransaction->commit(); - */ - virtual std::string multisigSignData() = 0; - virtual void signMultisigTx() = 0; - /** - * @brief signersKeys - * @return vector of base58-encoded signers' public keys - */ - virtual std::vector<std::string> signersKeys() const = 0; -}; - -/** - * @brief Transaction-like interface for sending money - */ -struct UnsignedTransaction -{ - enum Status { - Status_Ok, - Status_Error, - Status_Critical - }; - - virtual ~UnsignedTransaction() = 0; - virtual int status() const = 0; - virtual std::string errorString() const = 0; - virtual std::vector<uint64_t> amount() const = 0; - virtual std::vector<uint64_t> fee() const = 0; - virtual std::vector<uint64_t> mixin() const = 0; - // returns a string with information about all transactions. - virtual std::string confirmationMessage() const = 0; - virtual std::vector<std::string> paymentId() const = 0; - virtual std::vector<std::string> recipientAddress() const = 0; - virtual uint64_t minMixinCount() const = 0; - /*! - * \brief txCount - number of transactions current transaction will be splitted to - * \return - */ - virtual uint64_t txCount() const = 0; - /*! - * @brief sign - Sign txs and saves to file - * @param signedFileName - * return - true on success - */ - virtual bool sign(const std::string &signedFileName) = 0; -}; - -/** - * @brief The TransactionInfo - interface for displaying transaction information - */ -struct TransactionInfo -{ - enum Direction { - Direction_In, - Direction_Out - }; - - struct Transfer { - Transfer(uint64_t _amount, const std::string &address); - const uint64_t amount; - const std::string address; - }; - - virtual ~TransactionInfo() = 0; - virtual int direction() const = 0; - virtual bool isPending() const = 0; - virtual bool isFailed() const = 0; - virtual bool isCoinbase() const = 0; - virtual uint64_t amount() const = 0; - virtual uint64_t fee() const = 0; - virtual uint64_t blockHeight() const = 0; - virtual std::string description() const = 0; - virtual std::set<uint32_t> subaddrIndex() const = 0; - virtual uint32_t subaddrAccount() const = 0; - virtual std::string label() const = 0; - virtual uint64_t confirmations() const = 0; - virtual uint64_t unlockTime() const = 0; - //! transaction_id - virtual std::string hash() const = 0; - virtual std::time_t timestamp() const = 0; - virtual std::string paymentId() const = 0; - //! only applicable for output transactions - virtual const std::vector<Transfer> & transfers() const = 0; -}; -/** - * @brief The TransactionHistory - interface for displaying transaction history - */ -struct TransactionHistory -{ - virtual ~TransactionHistory() = 0; - virtual int count() const = 0; - virtual TransactionInfo * transaction(int index) const = 0; - virtual TransactionInfo * transaction(const std::string &id) const = 0; - virtual std::vector<TransactionInfo*> getAll() const = 0; - virtual void refresh() = 0; - virtual void setTxNote(const std::string &txid, const std::string ¬e) = 0; -}; - -/** - * @brief AddressBookRow - provides functions to manage address book - */ -struct AddressBookRow { -public: - AddressBookRow(std::size_t _rowId, const std::string &_address, const std::string &_paymentId, const std::string &_description): - m_rowId(_rowId), - m_address(_address), - m_paymentId(_paymentId), - m_description(_description) {} - -private: - std::size_t m_rowId; - std::string m_address; - std::string m_paymentId; - std::string m_description; -public: - std::string extra; - std::string getAddress() const {return m_address;} - std::string getDescription() const {return m_description;} - std::string getPaymentId() const {return m_paymentId;} - std::size_t getRowId() const {return m_rowId;} -}; - -/** - * @brief The AddressBook - interface for -Book - */ -struct AddressBook -{ - enum ErrorCode { - Status_Ok, - General_Error, - Invalid_Address, - Invalid_Payment_Id - }; - virtual ~AddressBook() = 0; - virtual std::vector<AddressBookRow*> getAll() const = 0; - virtual bool addRow(const std::string &dst_addr , const std::string &payment_id, const std::string &description) = 0; - virtual bool deleteRow(std::size_t rowId) = 0; - virtual bool setDescription(std::size_t index, const std::string &description) = 0; - virtual void refresh() = 0; - virtual std::string errorString() const = 0; - virtual int errorCode() const = 0; - virtual int lookupPaymentID(const std::string &payment_id) const = 0; -}; - -/** - * @brief The CoinsInfo - interface for displaying coins information - */ -struct CoinsInfo -{ - virtual ~CoinsInfo() = 0; - - virtual uint64_t blockHeight() const = 0; - virtual std::string hash() const = 0; - virtual size_t internalOutputIndex() const = 0; - virtual uint64_t globalOutputIndex() const = 0; - virtual bool spent() const = 0; - virtual bool frozen() const = 0; - virtual uint64_t spentHeight() const = 0; - virtual uint64_t amount() const = 0; - virtual bool rct() const = 0; - virtual bool keyImageKnown() const = 0; - virtual size_t pkIndex() const = 0; - virtual uint32_t subaddrIndex() const = 0; - virtual uint32_t subaddrAccount() const = 0; - virtual std::string address() const = 0; - virtual std::string addressLabel() const = 0; - virtual std::string keyImage() const = 0; - virtual uint64_t unlockTime() const = 0; - virtual bool unlocked() const = 0; - virtual std::string pubKey() const = 0; - virtual bool coinbase() const = 0; - virtual std::string description() const = 0; -}; - -struct Coins -{ - virtual ~Coins() = 0; - virtual int count() const = 0; - virtual CoinsInfo * coin(int index) const = 0; - virtual std::vector<CoinsInfo*> getAll() const = 0; - virtual void refresh() = 0; - virtual void setFrozen(std::string public_key) = 0; - virtual void setFrozen(int index) = 0; - virtual void thaw(std::string public_key) = 0; - virtual void thaw(int index) = 0; - virtual bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) = 0; - virtual void setDescription(const std::string &public_key, const std::string &description) = 0; -}; - -struct SubaddressRow { -public: - SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label): - m_rowId(_rowId), - m_address(_address), - m_label(_label) {} - -private: - std::size_t m_rowId; - std::string m_address; - std::string m_label; -public: - std::string extra; - std::string getAddress() const {return m_address;} - std::string getLabel() const {return m_label;} - std::size_t getRowId() const {return m_rowId;} -}; - -struct Subaddress -{ - virtual ~Subaddress() = 0; - virtual std::vector<SubaddressRow*> getAll() const = 0; - virtual void addRow(uint32_t accountIndex, const std::string &label) = 0; - virtual void setLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; - virtual void refresh(uint32_t accountIndex) = 0; -}; - -struct SubaddressAccountRow { -public: - SubaddressAccountRow(std::size_t _rowId, const std::string &_address, const std::string &_label, const std::string &_balance, const std::string &_unlockedBalance): - m_rowId(_rowId), - m_address(_address), - m_label(_label), - m_balance(_balance), - m_unlockedBalance(_unlockedBalance) {} - -private: - std::size_t m_rowId; - std::string m_address; - std::string m_label; - std::string m_balance; - std::string m_unlockedBalance; -public: - std::string extra; - std::string getAddress() const {return m_address;} - std::string getLabel() const {return m_label;} - std::string getBalance() const {return m_balance;} - std::string getUnlockedBalance() const {return m_unlockedBalance;} - std::size_t getRowId() const {return m_rowId;} -}; - -struct SubaddressAccount -{ - virtual ~SubaddressAccount() = 0; - virtual std::vector<SubaddressAccountRow*> getAll() const = 0; - virtual void addRow(const std::string &label) = 0; - virtual void setLabel(uint32_t accountIndex, const std::string &label) = 0; - virtual void refresh() = 0; -}; - -struct MultisigState { - MultisigState() : isMultisig(false), isReady(false), threshold(0), total(0) {} - - bool isMultisig; - bool isReady; - uint32_t threshold; - uint32_t total; -}; - - -struct DeviceProgress { - DeviceProgress(): m_progress(0), m_indeterminate(false) {} - DeviceProgress(double progress, bool indeterminate=false): m_progress(progress), m_indeterminate(indeterminate) {} - - virtual double progress() const { return m_progress; } - virtual bool indeterminate() const { return m_indeterminate; } - -protected: - double m_progress; - bool m_indeterminate; -}; - -struct Wallet; -struct WalletListener -{ - virtual ~WalletListener() = 0; - /** - * @brief moneySpent - called when money spent - * @param txId - transaction id - * @param amount - amount - */ - virtual void moneySpent(const std::string &txId, uint64_t amount) = 0; - - /** - * @brief moneyReceived - called when money received - * @param txId - transaction id - * @param amount - amount - */ - virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0; - - /** - * @brief unconfirmedMoneyReceived - called when payment arrived in tx pool - * @param txId - transaction id - * @param amount - amount - */ - virtual void unconfirmedMoneyReceived(const std::string &txId, uint64_t amount) = 0; - - /** - * @brief newBlock - called when new block received - * @param height - block height - */ - virtual void newBlock(uint64_t height) = 0; - - /** - * @brief updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet; - */ - virtual void updated() = 0; - - - /** - * @brief refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously - */ - virtual void refreshed() = 0; - - /** - * @brief called by device if the action is required - */ - virtual void onDeviceButtonRequest(uint64_t code) { (void)code; } - - /** - * @brief called by device if the button was pressed - */ - virtual void onDeviceButtonPressed() { } - - /** - * @brief called by device when PIN is needed - */ - virtual optional<std::string> onDevicePinRequest() { - throw std::runtime_error("Not supported"); - } - - /** - * @brief called by device when passphrase entry is needed - */ - virtual optional<std::string> onDevicePassphraseRequest(bool & on_device) { - on_device = true; - return optional<std::string>(); - } - - /** - * @brief Signalizes device operation progress - */ - virtual void onDeviceProgress(const DeviceProgress & event) { (void)event; }; - - /** - * @brief If the listener is created before the wallet this enables to set created wallet object - */ - virtual void onSetWallet(Wallet * wallet) { (void)wallet; }; -}; - - -/** - * @brief Interface for wallet operations. - */ -struct Wallet -{ - enum Device { - Device_Software = 0, - Device_Ledger = 1, - Device_Trezor = 2 - }; - - enum Status { - Status_Ok, - Status_Error, - Status_Critical - }; - - enum ConnectionStatus { - ConnectionStatus_Disconnected, - ConnectionStatus_Connected, - ConnectionStatus_WrongVersion - }; - - enum BackgroundSyncType { - BackgroundSync_Off = 0, - BackgroundSync_ReusePassword = 1, - BackgroundSync_CustomPassword = 2 - }; - - virtual ~Wallet() = 0; - virtual std::string seed(const std::string& seed_offset = "") const = 0; - virtual std::string getSeedLanguage() const = 0; - virtual void setSeedLanguage(const std::string &arg) = 0; - //! returns wallet status (Status_Ok | Status_Error) - virtual int status() const = 0; //deprecated: use safe alternative statusWithErrorString - //! in case error status, returns error string - virtual std::string errorString() const = 0; //deprecated: use safe alternative statusWithErrorString - //! returns both error and error string atomically. suggested to use in instead of status() and errorString() - virtual void statusWithErrorString(int& status, std::string& errorString) const = 0; - virtual bool setPassword(const std::string &password) = 0; - virtual const std::string& getPassword() const = 0; - virtual bool setDevicePin(const std::string &pin) { (void)pin; return false; }; - virtual bool setDevicePassphrase(const std::string &passphrase) { (void)passphrase; return false; }; - virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; - std::string mainAddress() const { return address(0, 0); } - virtual std::string path() const = 0; - virtual NetworkType nettype() const = 0; - bool mainnet() const { return nettype() == MAINNET; } - bool testnet() const { return nettype() == TESTNET; } - bool stagenet() const { return nettype() == STAGENET; } - //! returns current hard fork info - virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0; - //! check if hard fork rules should be used - virtual bool useForkRules(uint8_t version, int64_t early_blocks) 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 secretViewKey - returns secret view key - * \return - secret view key - */ - virtual std::string secretViewKey() const = 0; - - /*! - * \brief publicViewKey - returns public view key - * \return - public view key - */ - virtual std::string publicViewKey() const = 0; - - /*! - * \brief secretSpendKey - returns secret spend key - * \return - secret spend key - */ - virtual std::string secretSpendKey() const = 0; - - /*! - * \brief publicSpendKey - returns public spend key - * \return - public spend key - */ - virtual std::string publicSpendKey() const = 0; - - /*! - * \brief publicMultisigSignerKey - returns public signer key - * \return - public multisignature signer key or empty string if wallet is not multisig - */ - virtual std::string publicMultisigSignerKey() const = 0; - - /*! - * \brief stop - interrupts wallet refresh() loop once (doesn't stop background refresh thread) - */ - virtual void stop() = 0; - - /*! - * \brief store - stores wallet to file. - * \param path - main filename to store wallet to. additionally stores address file and keys file. - * to store to the same file - just pass empty string; - * \return - */ - virtual bool store(const std::string &path) = 0; - /*! - * \brief filename - returns wallet filename - * \return - */ - virtual std::string filename() const = 0; - /*! - * \brief keysFilename - returns keys filename. usually this formed as "wallet_filename".keys - * \return - */ - virtual std::string keysFilename() const = 0; - /*! - * \brief init - initializes wallet with daemon connection params. - * if daemon_address is local address, "trusted daemon" will be set to true forcibly - * startRefresh() should be called when wallet is initialized. - * - * \param daemon_address - daemon address in "hostname:port" format - * \param upper_transaction_size_limit - * \param daemon_username - * \param daemon_password - * \param lightWallet - start wallet in light mode, connect to a openmonero compatible server. - * \param proxy_address - set proxy address, empty string to disable - * \return - true on success - */ - virtual bool init(const std::string &daemon_address, uint64_t upper_transaction_size_limit = 0, const std::string &daemon_username = "", const std::string &daemon_password = "", bool use_ssl = false, bool lightWallet = false, const std::string &proxy_address = "") = 0; - - /*! - * \brief createWatchOnly - Creates a watch only wallet - * \param path - where to store the wallet - * \param password - * \param language - * \return - true if created successfully - */ - virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0; - - /*! - * \brief setRefreshFromBlockHeight - start refresh from block height on recover - * - * \param refresh_from_block_height - blockchain start height - */ - virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0; - - /*! - * \brief getRestoreHeight - get wallet creation height - * - */ - virtual uint64_t getRefreshFromBlockHeight() const = 0; - - /*! - * \brief setRecoveringFromSeed - set state recover form seed - * - * \param recoveringFromSeed - true/false - */ - virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0; - - /*! - * \brief setRecoveringFromDevice - set state to recovering from device - * - * \param recoveringFromDevice - true/false - */ - virtual void setRecoveringFromDevice(bool recoveringFromDevice) = 0; - - /*! - * \brief setSubaddressLookahead - set size of subaddress lookahead - * - * \param major - size fot the major index - * \param minor - size fot the minor index - */ - virtual void setSubaddressLookahead(uint32_t major, uint32_t minor) = 0; - - /** - * @brief connectToDaemon - connects to the daemon. TODO: check if it can be removed - * @return - */ - virtual bool connectToDaemon() = 0; - - /** - * @brief connected - checks if the wallet connected to the daemon - * @return - true if connected - */ - virtual ConnectionStatus connected() const = 0; - virtual void setTrustedDaemon(bool arg) = 0; - virtual bool trustedDaemon() const = 0; - virtual bool setProxy(const std::string &address) = 0; - virtual uint64_t balance(uint32_t accountIndex = 0) const = 0; - uint64_t balanceAll() const { - uint64_t result = 0; - for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) - result += balance(i); - return result; - } - virtual uint64_t unlockedBalance(uint32_t accountIndex = 0) const = 0; - uint64_t unlockedBalanceAll() const { - uint64_t result = 0; - for (uint32_t i = 0; i < numSubaddressAccounts(); ++i) - result += unlockedBalance(i); - return result; - } - virtual uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector<std::string> &key_images = {}) const = 0; - - /** - * @brief watchOnly - checks if wallet is watch only - * @return - true if watch only - */ - virtual bool watchOnly() const = 0; - - /** - * @brief isDeterministic - checks if wallet keys are deterministic - * @return - true if deterministic - */ - virtual bool isDeterministic() const = 0; - - /** - * @brief blockChainHeight - returns current blockchain height - * @return - */ - virtual uint64_t blockChainHeight() const = 0; - - /** - * @brief approximateBlockChainHeight - returns approximate blockchain height calculated from date/time - * @return - */ - virtual uint64_t approximateBlockChainHeight() const = 0; - - /** - * @brief estimateBlockChainHeight - returns estimate blockchain height. More accurate than approximateBlockChainHeight, - * uses daemon height and falls back to calculation from date/time - * @return - **/ - virtual uint64_t estimateBlockChainHeight() const = 0; - /** - * @brief daemonBlockChainHeight - returns daemon blockchain height - * @return 0 - in case error communicating with the daemon. - * status() will return Status_Error and errorString() will return verbose error description - */ - virtual uint64_t daemonBlockChainHeight() const = 0; - - /** - * @brief daemonBlockChainTargetHeight - returns daemon blockchain target height - * @return 0 - in case error communicating with the daemon. - * status() will return Status_Error and errorString() will return verbose error description - */ - virtual uint64_t daemonBlockChainTargetHeight() const = 0; - - /** - * @brief synchronized - checks if wallet was ever synchronized - * @return - */ - virtual bool synchronized() const = 0; - - 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(); - static bool paymentIdValid(const std::string &paiment_id); - static bool addressValid(const std::string &str, NetworkType nettype); - static bool addressValid(const std::string &str, bool testnet) // deprecated - { - return addressValid(str, testnet ? TESTNET : MAINNET); - } - static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, NetworkType nettype, std::string &error); - static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error) // deprecated - { - return keyValid(secret_key_string, address_string, isViewKey, testnet ? TESTNET : MAINNET, error); - } - static std::string paymentIdFromAddress(const std::string &str, NetworkType nettype); - static std::string paymentIdFromAddress(const std::string &str, bool testnet) // deprecated - { - return paymentIdFromAddress(str, testnet ? TESTNET : MAINNET); - } - static uint64_t maximumAllowedAmount(); - // Easylogger wrapper - static void init(const char *argv0, const char *default_log_base_name) { init(argv0, default_log_base_name, "", true); } - static void init(const char *argv0, const char *default_log_base_name, const std::string &log_path, bool console); - static void debug(const std::string &category, const std::string &str); - static void info(const std::string &category, const std::string &str); - static void warning(const std::string &category, const std::string &str); - static void error(const std::string &category, const std::string &str); - - virtual bool getPolyseed(std::string &seed, std::string &passphrase) const = 0; - static bool createPolyseed(std::string &seed_words, std::string &err, const std::string &language = "English"); - static std::vector<std::pair<std::string, std::string>> getPolyseedLanguages(); - - /** - * @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds) - */ - virtual void startRefresh() = 0; - /** - * @brief pauseRefresh - pause refresh thread - */ - virtual void pauseRefresh() = 0; - - /** - * @brief refresh - refreshes the wallet, updating transactions from daemon - * @return - true if refreshed successfully; - */ - virtual bool refresh() = 0; - - /** - * @brief refreshAsync - refreshes wallet asynchronously. - */ - virtual void refreshAsync() = 0; - - /** - * @brief rescanBlockchain - rescans the wallet, updating transactions from daemon - * @return - true if refreshed successfully; - */ - virtual bool rescanBlockchain() = 0; - - /** - * @brief rescanBlockchainAsync - rescans wallet asynchronously, starting from genesys - */ - virtual void rescanBlockchainAsync() = 0; - - /** - * @brief setAutoRefreshInterval - setup interval for automatic refresh. - * @param seconds - interval in millis. if zero or less than zero - automatic refresh disabled; - */ - virtual void setAutoRefreshInterval(int millis) = 0; - - /** - * @brief autoRefreshInterval - returns automatic refresh interval in millis - * @return - */ - virtual int autoRefreshInterval() const = 0; - - /** - * @brief addSubaddressAccount - appends a new subaddress account at the end of the last major index of existing subaddress accounts - * @param label - the label for the new account (which is the as the label of the primary address (accountIndex,0)) - */ - virtual void addSubaddressAccount(const std::string& label) = 0; - /** - * @brief numSubaddressAccounts - returns the number of existing subaddress accounts - */ - virtual size_t numSubaddressAccounts() const = 0; - /** - * @brief numSubaddresses - returns the number of existing subaddresses associated with the specified subaddress account - * @param accountIndex - the major index specifying the subaddress account - */ - virtual size_t numSubaddresses(uint32_t accountIndex) const = 0; - /** - * @brief addSubaddress - appends a new subaddress at the end of the last minor index of the specified subaddress account - * @param accountIndex - the major index specifying the subaddress account - * @param label - the label for the new subaddress - */ - virtual void addSubaddress(uint32_t accountIndex, const std::string& label) = 0; - /** - * @brief getSubaddressLabel - gets the label of the specified subaddress - * @param accountIndex - the major index specifying the subaddress account - * @param addressIndex - the minor index specifying the subaddress - */ - virtual std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const = 0; - /** - * @brief setSubaddressLabel - sets the label of the specified subaddress - * @param accountIndex - the major index specifying the subaddress account - * @param addressIndex - the minor index specifying the subaddress - * @param label - the new label for the specified subaddress - */ - virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; - - /** - * @brief multisig - returns current state of multisig wallet creation process - * @return MultisigState struct - */ - virtual MultisigState multisig() const = 0; - /** - * @brief getMultisigInfo - * @return serialized and signed multisig info string - */ - virtual std::string getMultisigInfo() const = 0; - /** - * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets - * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call - * @param threshold - number of required signers to make valid transaction. Must be <= number of participants - * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info - */ - virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; - /** - * @brief exchange_multisig_keys - provides additional key exchange round for arbitrary multisig schemes (like N-1/N, M/N) - * @param info - base58 encoded key derivations returned by makeMultisig or exchangeMultisigKeys function call - * @param force_update_use_with_caution - force multisig account to update even if not all signers contribute round messages - * @return new info string if more rounds required or an empty string if wallet creation is done - */ - virtual std::string exchangeMultisigKeys(const std::vector<std::string> &info, const bool force_update_use_with_caution) = 0; - /** - * @brief exportMultisigImages - exports transfers' key images - * @param images - output paramter for hex encoded array of images - * @return true if success - */ - virtual bool exportMultisigImages(std::string& images) = 0; - /** - * @brief importMultisigImages - imports other participants' multisig images - * @param images - array of hex encoded arrays of images obtained with exportMultisigImages - * @return number of imported images - */ - virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0; - /** - * @brief hasMultisigPartialKeyImages - checks if wallet needs to import multisig key images from other participants - * @return true if there are partial key images - */ - virtual bool hasMultisigPartialKeyImages() const = 0; - - /** - * @brief restoreMultisigTransaction creates PendingTransaction from signData - * @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData - * @return PendingTransaction - */ - virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0; - - /*! - * \brief createTransactionMultDest creates transaction with multiple destinations. if dst_addr is an integrated address, payment_id is ignored - * \param dst_addr vector of destination address as string - * \param payment_id optional payment_id, can be empty string - * \param amount vector of amounts - * \param mixin_count mixin count. if 0 passed, wallet will use default value - * \param subaddr_account subaddress account from which the input funds are taken - * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices - * \param priority - * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() - * after object returned - */ - - virtual PendingTransaction * createTransactionMultDest(const std::vector<std::string> &dst_addr, const std::string &payment_id, - optional<std::vector<uint64_t>> amount, uint32_t mixin_count, - PendingTransaction::Priority = PendingTransaction::Priority_Low, - uint32_t subaddr_account = 0, - std::set<uint32_t> subaddr_indices = {}, - const std::set<std::string> &preferred_inputs = {}) = 0; - - /*! - * \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 - * \param subaddr_account subaddress account from which the input funds are taken - * \param subaddr_indices set of subaddress indices to use for transfer or sweeping. if set empty, all are chosen when sweeping, and one or more are automatically chosen when transferring. after execution, returns the set of actually used indices - * \param priority - * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() - * after object returned - */ - - virtual PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, - optional<uint64_t> amount, uint32_t mixin_count, - PendingTransaction::Priority = PendingTransaction::Priority_Low, - uint32_t subaddr_account = 0, - std::set<uint32_t> subaddr_indices = {}, - const std::set<std::string> &preferred_inputs = {}) = 0; - - /*! - * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. - * \return PendingTransaction object. caller is responsible to check PendingTransaction::status() - * after object returned - */ - - virtual PendingTransaction * createSweepUnmixableTransaction() = 0; - - /*! - * \brief loadUnsignedTx - creates transaction from unsigned tx file - * \return - UnsignedTransaction object. caller is responsible to check UnsignedTransaction::status() - * after object returned - */ - virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; - - /*! - * \brief submitTransaction - submits transaction in signed tx file - * \return - true on success - */ - virtual bool submitTransaction(const std::string &fileName) = 0; - - - /*! - * \brief disposeTransaction - destroys transaction object - * \param t - pointer to the "PendingTransaction" object. Pointer is not valid after function returned; - */ - virtual void disposeTransaction(PendingTransaction * t) = 0; - - /*! - * \brief Estimates transaction fee. - * \param destinations Vector consisting of <address, amount> pairs. - * \return Estimated fee. - */ - virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, - PendingTransaction::Priority priority) const = 0; - - virtual bool hasUnknownKeyImages() const = 0; - - /*! - * \brief exportKeyImages - exports key images to file - * \param filename - * \param all - export all key images or only those that have not yet been exported - * \return - true on success - */ - virtual bool exportKeyImages(const std::string &filename, bool all = false) = 0; - - /*! - * \brief importKeyImages - imports key images from file - * \param filename - * \return - true on success - */ - virtual bool importKeyImages(const std::string &filename) = 0; - - /*! - * \brief importOutputs - exports outputs to file - * \param filename - * \return - true on success - */ - virtual bool exportOutputs(const std::string &filename, bool all = false) = 0; - - /*! - * \brief importOutputs - imports outputs from file - * \param filename - * \return - true on success - */ - virtual bool importOutputs(const std::string &filename) = 0; - - /*! - * \brief scanTransactions - scan a list of transaction ids, this operation may reveal the txids to the remote node and affect your privacy - * \param txids - list of transaction ids - * \return - true on success - */ - virtual bool scanTransactions(const std::vector<std::string> &txids) = 0; - - /*! - * \brief setupBackgroundSync - setup background sync mode with just a view key - * \param background_sync_type - the mode the wallet background syncs in - * \param wallet_password - * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type - * \return - true on success - */ - virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) = 0; - - /*! - * \brief getBackgroundSyncType - get mode the wallet background syncs in - * \return - the type, or off if type is unknown - */ - virtual BackgroundSyncType getBackgroundSyncType() const = 0; - - /** - * @brief startBackgroundSync - sync the chain in the background with just view key - */ - virtual bool startBackgroundSync() = 0; - - /** - * @brief stopBackgroundSync - bring back spend key and process background synced txs - * \param wallet_password - */ - virtual bool stopBackgroundSync(const std::string &wallet_password) = 0; - - /** - * @brief isBackgroundSyncing - returns true if the wallet is background syncing - */ - virtual bool isBackgroundSyncing() const = 0; - - /** - * @brief isBackgroundWallet - returns true if the wallet is a background wallet - */ - virtual bool isBackgroundWallet() const = 0; - - - virtual TransactionHistory * history() = 0; - virtual AddressBook * addressBook() = 0; - virtual Coins * coins() = 0; - virtual Subaddress * subaddress() = 0; - virtual SubaddressAccount * subaddressAccount() = 0; - virtual void setListener(WalletListener *) = 0; - /*! - * \brief defaultMixin - returns number of mixins used in transactions - * \return - */ - virtual uint32_t defaultMixin() const = 0; - /*! - * \brief setDefaultMixin - setum number of mixins to be used for new transactions - * \param arg - */ - virtual void setDefaultMixin(uint32_t arg) = 0; - - /*! - * \brief setCacheAttribute - attach an arbitrary string to a wallet cache attribute - * \param key - the key - * \param val - the value - * \return true if successful, false otherwise - */ - virtual bool setCacheAttribute(const std::string &key, const std::string &val) = 0; - /*! - * \brief getCacheAttribute - return an arbitrary string attached to a wallet cache attribute - * \param key - the key - * \return the attached string, or empty string if there is none - */ - virtual std::string getCacheAttribute(const std::string &key) const = 0; - /*! - * \brief setUserNote - attach an arbitrary string note to a txid - * \param txid - the transaction id to attach the note to - * \param note - the note - * \return true if successful, false otherwise - */ - virtual bool setUserNote(const std::string &txid, const std::string ¬e) = 0; - /*! - * \brief getUserNote - return an arbitrary string note attached to a txid - * \param txid - the transaction id to attach the note to - * \return the attached note, or empty string if there is none - */ - virtual std::string getUserNote(const std::string &txid) const = 0; - virtual std::string getTxKey(const std::string &txid) const = 0; - virtual bool checkTxKey(const std::string &txid, std::string tx_key, const std::string &address, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; - virtual std::string getTxProof(const std::string &txid, const std::string &address, const std::string &message) const = 0; - virtual bool checkTxProof(const std::string &txid, const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &received, bool &in_pool, uint64_t &confirmations) = 0; - virtual std::string getSpendProof(const std::string &txid, const std::string &message) const = 0; - virtual bool checkSpendProof(const std::string &txid, const std::string &message, const std::string &signature, bool &good) const = 0; - /*! - * \brief getReserveProof - Generates a proof that proves the reserve of unspent funds - * Parameters `account_index` and `amount` are ignored when `all` is true - */ - virtual std::string getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const = 0; - virtual bool checkReserveProof(const std::string &address, const std::string &message, const std::string &signature, bool &good, uint64_t &total, uint64_t &spent) const = 0; - - /* - * \brief signMessage - sign a message with the spend private key - * \param message - the message to sign (arbitrary byte data) - * \return the signature - */ - virtual std::string signMessage(const std::string &message, const std::string &address = "") = 0; - /*! - * \brief verifySignedMessage - verify a signature matches a given message - * \param message - the message (arbitrary byte data) - * \param address - the address the signature claims to be made with - * \param signature - the signature - * \return true if the signature verified, false otherwise - */ - virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0; - - /*! - * \brief signMultisigParticipant signs given message with the multisig public signer key - * \param message message to sign - * \return signature in case of success. Sets status to Error and return empty string in case of error - */ - virtual std::string signMultisigParticipant(const std::string &message) const = 0; - /*! - * \brief verifyMessageWithPublicKey verifies that message was signed with the given public key - * \param message message - * \param publicKey hex encoded public key - * \param signature signature of the message - * \return true if the signature is correct. false and sets error state in case of error - */ - virtual bool verifyMessageWithPublicKey(const std::string &message, const std::string &publicKey, const std::string &signature) const = 0; - - virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0; - virtual std::string make_uri(const std::string &address, const std::string &payment_id, uint64_t amount, const std::string &tx_description, const std::string &recipient_name, std::string &error) const = 0; - - virtual std::string getDefaultDataDir() const = 0; - - /* - * \brief rescanSpent - Rescan spent outputs - Can only be used with trusted daemon - * \return true on success - */ - virtual bool rescanSpent() = 0; - - /* - * \brief setOffline - toggle set offline on/off - * \param offline - true/false - */ - virtual void setOffline(bool offline) = 0; - virtual bool isOffline() const = 0; - - //! blackballs a set of outputs - virtual bool blackballOutputs(const std::vector<std::string> &outputs, bool add) = 0; - - //! blackballs an output - virtual bool blackballOutput(const std::string &amount, const std::string &offset) = 0; - - //! unblackballs an output - virtual bool unblackballOutput(const std::string &amount, const std::string &offset) = 0; - - //! gets the ring used for a key image, if any - virtual bool getRing(const std::string &key_image, std::vector<uint64_t> &ring) const = 0; - - //! gets the rings used for a txid, if any - virtual bool getRings(const std::string &txid, std::vector<std::pair<std::string, std::vector<uint64_t>>> &rings) const = 0; - - //! sets the ring used for a key image - virtual bool setRing(const std::string &key_image, const std::vector<uint64_t> &ring, bool relative) = 0; - - //! sets whether pre-fork outs are to be segregated - virtual void segregatePreForkOutputs(bool segregate) = 0; - - //! sets the height where segregation should occur - virtual void segregationHeight(uint64_t height) = 0; - - //! secondary key reuse mitigation - virtual void keyReuseMitigation2(bool mitigation) = 0; - - //! Light wallet authenticate and login - virtual bool lightWalletLogin(bool &isNewWallet) const = 0; - - //! Initiates a light wallet import wallet request - virtual bool lightWalletImportWalletRequest(std::string &payment_id, uint64_t &fee, bool &new_request, bool &request_fulfilled, std::string &payment_address, std::string &status) = 0; - - //! locks/unlocks the keys file; returns true on success - virtual bool lockKeysFile() = 0; - virtual bool unlockKeysFile() = 0; - //! returns true if the keys file is locked - virtual bool isKeysFileLocked() = 0; - - /*! - * \brief Queries backing device for wallet keys - * \return Device they are on - */ - virtual Device getDeviceType() const = 0; - - //! cold-device protocol key image sync - virtual uint64_t coldKeyImageSync(uint64_t &spent, uint64_t &unspent) = 0; - - //! shows address on device display - virtual void deviceShowAddress(uint32_t accountIndex, uint32_t addressIndex, const std::string &paymentId) = 0; - - //! attempt to reconnect to hardware device - virtual bool reconnectDevice() = 0; - - //! get bytes received - virtual uint64_t getBytesReceived() = 0; - - //! get bytes sent - virtual uint64_t getBytesSent() = 0; -}; - -/** - * @brief WalletManager - provides functions to manage wallets - */ -struct WalletManager -{ - - /*! - * \brief Creates new wallet - * \param path Name of wallet file - * \param password Password of wallet file - * \param language Language to be used to generate electrum seed mnemonic - * \param nettype Network type - * \param kdf_rounds Number of rounds for key derivation function - * \return Wallet instance (Wallet::status() needs to be called to check if created successfully) - */ - virtual Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, NetworkType nettype, uint64_t kdf_rounds = 1) = 0; - Wallet * createWallet(const std::string &path, const std::string &password, const std::string &language, bool testnet = false) // deprecated - { - return createWallet(path, password, language, testnet ? TESTNET : MAINNET); - } - - /*! - * \brief Opens existing wallet - * \param path Name of wallet file - * \param password Password of wallet file - * \param nettype Network type - * \param kdf_rounds Number of rounds for key derivation function - * \param listener Wallet listener to set to the wallet after creation - * \return Wallet instance (Wallet::status() needs to be called to check if opened successfully) - */ - virtual Wallet * openWallet(const std::string &path, const std::string &password, NetworkType nettype, uint64_t kdf_rounds = 1, WalletListener * listener = nullptr) = 0; - Wallet * openWallet(const std::string &path, const std::string &password, bool testnet = false) // deprecated - { - return openWallet(path, password, testnet ? TESTNET : MAINNET); - } - - /*! - * \brief recovers existing wallet using mnemonic (electrum seed) - * \param path Name of wallet file to be created - * \param password Password of wallet file - * \param mnemonic mnemonic (25 words electrum seed) - * \param nettype Network type - * \param restoreHeight restore from start height - * \param kdf_rounds Number of rounds for key derivation function - * \param seed_offset Seed offset passphrase (optional) - * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) - */ - virtual Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, - NetworkType nettype = MAINNET, uint64_t restoreHeight = 0, uint64_t kdf_rounds = 1, - const std::string &seed_offset = {}) = 0; - Wallet * recoveryWallet(const std::string &path, const std::string &password, const std::string &mnemonic, - bool testnet = false, uint64_t restoreHeight = 0) // deprecated - { - return recoveryWallet(path, password, mnemonic, testnet ? TESTNET : MAINNET, restoreHeight); - } - - /*! - * \deprecated this method creates a wallet WITHOUT a passphrase, use the alternate recoverWallet() method - * \brief recovers existing wallet using mnemonic (electrum seed) - * \param path Name of wallet file to be created - * \param mnemonic mnemonic (25 words electrum seed) - * \param nettype Network type - * \param restoreHeight restore from start height - * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) - */ - virtual Wallet * recoveryWallet(const std::string &path, const std::string &mnemonic, NetworkType nettype, uint64_t restoreHeight = 0) = 0; - Wallet * recoveryWallet(const std::string &path, const std::string &mnemonic, bool testnet = false, uint64_t restoreHeight = 0) // deprecated - { - return recoveryWallet(path, mnemonic, testnet ? TESTNET : MAINNET, restoreHeight); - } - - /*! - * \brief recovers existing wallet using keys. Creates a view only wallet if spend key is omitted - * \param path Name of wallet file to be created - * \param password Password of wallet file - * \param language language - * \param nettype Network type - * \param restoreHeight restore from start height - * \param addressString public address - * \param viewKeyString view key - * \param spendKeyString spend key (optional) - * \param kdf_rounds Number of rounds for key derivation function - * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) - */ - virtual Wallet * createWalletFromKeys(const std::string &path, - const std::string &password, - const std::string &language, - NetworkType nettype, - uint64_t restoreHeight, - const std::string &addressString, - const std::string &viewKeyString, - const std::string &spendKeyString = "", - uint64_t kdf_rounds = 1) = 0; - Wallet * createWalletFromKeys(const std::string &path, - const std::string &password, - const std::string &language, - bool testnet, - uint64_t restoreHeight, - const std::string &addressString, - const std::string &viewKeyString, - const std::string &spendKeyString = "") // deprecated - { - return createWalletFromKeys(path, password, language, testnet ? TESTNET : MAINNET, restoreHeight, addressString, viewKeyString, spendKeyString); - } - - /*! - * \deprecated this method creates a wallet WITHOUT a passphrase, use createWalletFromKeys(..., password, ...) instead - * \brief recovers existing wallet using keys. Creates a view only wallet if spend key is omitted - * \param path Name of wallet file to be created - * \param language language - * \param nettype Network type - * \param restoreHeight restore from start height - * \param addressString public address - * \param viewKeyString view key - * \param spendKeyString spend key (optional) - * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) - */ - virtual Wallet * createWalletFromKeys(const std::string &path, - const std::string &language, - NetworkType nettype, - uint64_t restoreHeight, - const std::string &addressString, - const std::string &viewKeyString, - const std::string &spendKeyString = "") = 0; - Wallet * createWalletFromKeys(const std::string &path, - const std::string &language, - bool testnet, - uint64_t restoreHeight, - const std::string &addressString, - const std::string &viewKeyString, - const std::string &spendKeyString = "") // deprecated - { - return createWalletFromKeys(path, language, testnet ? TESTNET : MAINNET, restoreHeight, addressString, viewKeyString, spendKeyString); - } - - /*! - * \brief creates wallet using hardware device. - * \param path Name of wallet file to be created - * \param password Password of wallet file - * \param nettype Network type - * \param deviceName Device name - * \param restoreHeight restore from start height (0 sets to current height) - * \param subaddressLookahead Size of subaddress lookahead (empty sets to some default low value) - * \param kdf_rounds Number of rounds for key derivation function - * \param listener Wallet listener to set to the wallet after creation - * \return Wallet instance (Wallet::status() needs to be called to check if recovered successfully) - */ - virtual Wallet * createWalletFromDevice(const std::string &path, - const std::string &password, - NetworkType nettype, - const std::string &deviceName, - uint64_t restoreHeight = 0, - const std::string &subaddressLookahead = "", - uint64_t kdf_rounds = 1, - WalletListener * listener = nullptr) = 0; - - /*! - * \brief creates a wallet from a polyseed mnemonic phrase - * \param path Name of the wallet file to be created - * \param password Password of wallet file - * \param nettype Network type - * \param mnemonic Polyseed mnemonic - * \param passphrase Optional seed offset passphrase - * \param newWallet Whether it is a new wallet - * \param restoreHeight Override the embedded restore height - * \param kdf_rounds Number of rounds for key derivation function - * @return - */ - virtual Wallet * createWalletFromPolyseed(const std::string &path, - const std::string &password, - NetworkType nettype, - const std::string &mnemonic, - const std::string &passphrase = "", - bool newWallet = true, - uint64_t restore_height = 0, - uint64_t kdf_rounds = 1) = 0; - - /*! - * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted - * \param wallet previously opened / created wallet instance - * \return None - */ - virtual bool closeWallet(Wallet *wallet, bool store = true) = 0; - - /* - * ! checks if wallet with the given name already exists - */ - - /*! - * @brief TODO: delme walletExists - check if the given filename is the wallet - * @param path - filename - * @return - true if wallet exists - */ - virtual bool walletExists(const std::string &path) = 0; - - /*! - * @brief verifyWalletPassword - check if the given filename is the wallet - * @param keys_file_name - location of keys file - * @param password - password to verify - * @param no_spend_key - verify only view keys? - * @param kdf_rounds - number of rounds for key derivation function - * @return - true if password is correct - * - * @note - * This function will fail when the wallet keys file is opened because the wallet program locks the keys file. - * In this case, Wallet::unlockKeysFile() and Wallet::lockKeysFile() need to be called before and after the call to this function, respectively. - */ - virtual bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const = 0; - - /*! - * \brief determine the key storage for the specified wallet file - * \param device_type (OUT) wallet backend as enumerated in Wallet::Device - * \param keys_file_name Keys file to verify password for - * \param password Password to verify - * \return true if password correct, else false - * - * for verification only - determines key storage hardware - * - */ - virtual bool queryWalletDevice(Wallet::Device& device_type, const std::string &keys_file_name, const std::string &password, uint64_t kdf_rounds = 1) const = 0; - - /*! - * \brief findWallets - searches for the wallet files by given path name recursively - * \param path - starting point to search - * \return - list of strings with found wallets (absolute paths); - */ - virtual std::vector<std::string> findWallets(const std::string &path) = 0; - - //! returns verbose error string regarding last error; - virtual std::string errorString() const = 0; - - //! set the daemon address (hostname and port) - virtual void setDaemonAddress(const std::string &address) = 0; - - //! returns whether the daemon can be reached, and its version number - virtual bool connected(uint32_t *version = NULL) = 0; - - //! returns current blockchain height - virtual uint64_t blockchainHeight() = 0; - - //! returns current blockchain target height - virtual uint64_t blockchainTargetHeight() = 0; - - //! returns current network difficulty - virtual uint64_t networkDifficulty() = 0; - - //! returns current mining hash rate (0 if not mining) - virtual double miningHashRate() = 0; - - //! returns current block target - virtual uint64_t blockTarget() = 0; - - //! returns true iff mining - virtual bool isMining() = 0; - - //! starts mining with the set number of threads - virtual bool startMining(const std::string &address, uint32_t threads = 1, bool background_mining = false, bool ignore_battery = true) = 0; - - //! stops mining - virtual bool stopMining() = 0; - - //! resolves an OpenAlias address to a monero address - virtual std::string resolveOpenAlias(const std::string &address, bool &dnssec_valid) const = 0; - - //! checks for an update and returns version, hash and url - static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates( - const std::string &software, - std::string subdir, - const char *buildtag = nullptr, - const char *current_version = nullptr); - - //! sets proxy address, empty string to disable - virtual bool setProxy(const std::string &address) = 0; -}; - - -struct WalletManagerFactory -{ - // logging levels for underlying library - enum LogLevel { - LogLevel_Silent = -1, - LogLevel_0 = 0, - LogLevel_1 = 1, - LogLevel_2 = 2, - LogLevel_3 = 3, - LogLevel_4 = 4, - LogLevel_Min = LogLevel_Silent, - LogLevel_Max = LogLevel_4 - }; - - static WalletManager * getWalletManager(); - static void setLogLevel(int level); - static void setLogCategories(const std::string &categories); -}; - - -} +../../../../monero/src/wallet/api/wallet2_api.h
\ No newline at end of file diff --git a/monero b/monero new file mode 160000 +Subproject b7a695cf4b41645a5f5c5a5c1f0d565b283a358 diff --git a/patches/0000-polyseed.patch b/patches/0000-polyseed.patch new file mode 100644 index 0000000..7513399 --- /dev/null +++ b/patches/0000-polyseed.patch @@ -0,0 +1,1302 @@ +From edd49593044dc861b5fb4b9bae56a7636199aeaf Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto <cyjan@mrcyjanek.net> +Date: Tue, 12 Mar 2024 09:42:37 +0100 +Subject: [PATCH] PATCH: polyseed + +--- + .github/workflows/build.yml | 4 +- + .gitmodules | 6 + + CMakeLists.txt | 4 +- + contrib/epee/include/wipeable_string.h | 7 + + contrib/epee/src/wipeable_string.cpp | 10 ++ + external/CMakeLists.txt | 2 + + external/polyseed | 1 + + external/utf8proc | 1 + + src/CMakeLists.txt | 1 + + src/cryptonote_basic/CMakeLists.txt | 1 + + src/cryptonote_basic/account.cpp | 23 +++- + src/cryptonote_basic/account.h | 6 + + src/cryptonote_config.h | 2 + + src/polyseed/CMakeLists.txt | 25 ++++ + src/polyseed/pbkdf2.c | 85 ++++++++++++ + src/polyseed/pbkdf2.h | 46 +++++++ + src/polyseed/polyseed.cpp | 182 +++++++++++++++++++++++++ + src/polyseed/polyseed.hpp | 167 +++++++++++++++++++++++ + src/wallet/api/wallet.cpp | 71 ++++++++++ + src/wallet/api/wallet.h | 10 ++ + src/wallet/api/wallet2_api.h | 25 ++++ + src/wallet/api/wallet_manager.cpp | 9 ++ + src/wallet/api/wallet_manager.h | 10 ++ + src/wallet/wallet2.cpp | 102 ++++++++++++-- + src/wallet/wallet2.h | 30 +++- + 25 files changed, 809 insertions(+), 21 deletions(-) + create mode 160000 external/polyseed + create mode 160000 external/utf8proc + create mode 100644 src/polyseed/CMakeLists.txt + create mode 100644 src/polyseed/pbkdf2.c + create mode 100644 src/polyseed/pbkdf2.h + create mode 100644 src/polyseed/polyseed.cpp + create mode 100644 src/polyseed/polyseed.hpp + +diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml +index 4c1e381c0..70bea03b3 100644 +--- a/.github/workflows/build.yml ++++ b/.github/workflows/build.yml +@@ -124,8 +124,8 @@ jobs: + - name: build + run: | + ${{env.CCACHE_SETTINGS}} +- cmake . +- make wallet_api -j3 ++ cmake -S . -B build ++ cmake --build build wallet_api -j3 + + test-ubuntu: + needs: build-ubuntu +diff --git a/.gitmodules b/.gitmodules +index 721cce3b4..73a23fb35 100644 +--- a/.gitmodules ++++ b/.gitmodules +@@ -10,6 +10,12 @@ + [submodule "external/randomx"] + path = external/randomx + url = https://github.com/tevador/RandomX ++[submodule "external/utf8proc"] ++ path = external/utf8proc ++ url = https://github.com/JuliaStrings/utf8proc.git ++[submodule "external/polyseed"] ++ path = external/polyseed ++ url = https://github.com/tevador/polyseed.git + [submodule "external/supercop"] + path = external/supercop + url = https://github.com/monero-project/supercop +diff --git a/CMakeLists.txt b/CMakeLists.txt +index 8fb03ba1f..63b8c5079 100644 +--- a/CMakeLists.txt ++++ b/CMakeLists.txt +@@ -369,6 +369,8 @@ if(NOT MANUAL_SUBMODULES) + check_submodule(external/trezor-common) + check_submodule(external/randomx) + check_submodule(external/supercop) ++ check_submodule(external/polyseed) ++ check_submodule(external/utf8proc) + endif() + endif() + +@@ -458,7 +460,7 @@ endif() + # elseif(CMAKE_SYSTEM_NAME MATCHES ".*BSDI.*") + # set(BSDI TRUE) + +-include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include) ++include_directories(external/rapidjson/include external/easylogging++ src contrib/epee/include external external/supercop/include external/polyseed/include external/utf8proc) + + if(APPLE) + cmake_policy(SET CMP0042 NEW) +diff --git a/contrib/epee/include/wipeable_string.h b/contrib/epee/include/wipeable_string.h +index 65977cd97..594e15de4 100644 +--- a/contrib/epee/include/wipeable_string.h ++++ b/contrib/epee/include/wipeable_string.h +@@ -34,6 +34,7 @@ + #include <string> + #include "memwipe.h" + #include "fnv1.h" ++#include "serialization/keyvalue_serialization.h" + + namespace epee + { +@@ -75,6 +76,12 @@ namespace epee + bool operator!=(const wipeable_string &other) const noexcept { return buffer != other.buffer; } + wipeable_string &operator=(wipeable_string &&other); + wipeable_string &operator=(const wipeable_string &other); ++ char& operator[](size_t idx); ++ const char& operator[](size_t idx) const; ++ ++ BEGIN_KV_SERIALIZE_MAP() ++ KV_SERIALIZE_CONTAINER_POD_AS_BLOB(buffer) ++ END_KV_SERIALIZE_MAP() + + private: + void grow(size_t sz, size_t reserved = 0); +diff --git a/contrib/epee/src/wipeable_string.cpp b/contrib/epee/src/wipeable_string.cpp +index b016f2f48..f2f365b1b 100644 +--- a/contrib/epee/src/wipeable_string.cpp ++++ b/contrib/epee/src/wipeable_string.cpp +@@ -261,4 +261,14 @@ wipeable_string &wipeable_string::operator=(const wipeable_string &other) + return *this; + } + ++char& wipeable_string::operator[](size_t idx) { ++ CHECK_AND_ASSERT_THROW_MES(idx < buffer.size(), "Index out of bounds"); ++ return buffer[idx]; ++} ++ ++const char& wipeable_string::operator[](size_t idx) const { ++ CHECK_AND_ASSERT_THROW_MES(idx < buffer.size(), "Index out of bounds"); ++ return buffer[idx]; ++} ++ + } +diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt +index 5b7f69a56..1b9761d70 100644 +--- a/external/CMakeLists.txt ++++ b/external/CMakeLists.txt +@@ -70,3 +70,5 @@ add_subdirectory(db_drivers) + add_subdirectory(easylogging++) + add_subdirectory(qrcodegen) + add_subdirectory(randomx EXCLUDE_FROM_ALL) ++add_subdirectory(polyseed EXCLUDE_FROM_ALL) ++add_subdirectory(utf8proc EXCLUDE_FROM_ALL) +\ No newline at end of file +diff --git a/external/polyseed b/external/polyseed +new file mode 160000 +index 000000000..b7c35bb3c +--- /dev/null ++++ b/external/polyseed +@@ -0,0 +1 @@ ++Subproject commit b7c35bb3c6b91e481ecb04fc235eaff69c507fa1 +diff --git a/external/utf8proc b/external/utf8proc +new file mode 160000 +index 000000000..1cb28a66c +--- /dev/null ++++ b/external/utf8proc +@@ -0,0 +1 @@ ++Subproject commit 1cb28a66ca79a0845e99433fd1056257456cef8b +diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt +index 3335d3c21..06b708cf0 100644 +--- a/src/CMakeLists.txt ++++ b/src/CMakeLists.txt +@@ -95,6 +95,7 @@ add_subdirectory(net) + add_subdirectory(hardforks) + add_subdirectory(blockchain_db) + add_subdirectory(mnemonics) ++add_subdirectory(polyseed) + add_subdirectory(rpc) + if(NOT IOS) + add_subdirectory(serialization) +diff --git a/src/cryptonote_basic/CMakeLists.txt b/src/cryptonote_basic/CMakeLists.txt +index 1414be1b2..414936a05 100644 +--- a/src/cryptonote_basic/CMakeLists.txt ++++ b/src/cryptonote_basic/CMakeLists.txt +@@ -71,6 +71,7 @@ target_link_libraries(cryptonote_basic + checkpoints + cryptonote_format_utils_basic + device ++ polyseed_wrapper + ${Boost_DATE_TIME_LIBRARY} + ${Boost_PROGRAM_OPTIONS_LIBRARY} + ${Boost_SERIALIZATION_LIBRARY} +diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp +index 2ac455fda..4931c3740 100644 +--- a/src/cryptonote_basic/account.cpp ++++ b/src/cryptonote_basic/account.cpp +@@ -87,12 +87,16 @@ DISABLE_VS_WARNINGS(4244 4345) + void account_keys::xor_with_key_stream(const crypto::chacha_key &key) + { + // encrypt a large enough byte stream with chacha20 +- epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (2 + m_multisig_keys.size())); ++ epee::wipeable_string key_stream = get_key_stream(key, m_encryption_iv, sizeof(crypto::secret_key) * (3 + m_multisig_keys.size()) + m_passphrase.size()); + const char *ptr = key_stream.data(); + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_spend_secret_key.data[i] ^= *ptr++; + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) + m_view_secret_key.data[i] ^= *ptr++; ++ for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) ++ m_polyseed.data[i] ^= *ptr++; ++ for (size_t i = 0; i < m_passphrase.size(); ++i) ++ m_passphrase.data()[i] ^= *ptr++; + for (crypto::secret_key &k: m_multisig_keys) + { + for (size_t i = 0; i < sizeof(crypto::secret_key); ++i) +@@ -150,6 +154,8 @@ DISABLE_VS_WARNINGS(4244 4345) + { + m_keys.m_spend_secret_key = crypto::secret_key(); + m_keys.m_multisig_keys.clear(); ++ m_keys.m_polyseed = crypto::secret_key(); ++ m_keys.m_passphrase.wipe(); + } + //----------------------------------------------------------------- + crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) +@@ -244,6 +250,21 @@ DISABLE_VS_WARNINGS(4244 4345) + create_from_keys(address, fake, viewkey); + } + //----------------------------------------------------------------- ++ void account_base::create_from_polyseed(const polyseed::data& seed, const epee::wipeable_string &passphrase) ++ { ++ crypto::secret_key secret_key; ++ seed.keygen(&secret_key, sizeof(secret_key)); ++ ++ if (!passphrase.empty()) { ++ secret_key = cryptonote::decrypt_key(secret_key, passphrase); ++ } ++ ++ generate(secret_key, true, false); ++ ++ seed.save(m_keys.m_polyseed.data); ++ m_keys.m_passphrase = passphrase; ++ } ++ //----------------------------------------------------------------- + bool account_base::make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys) + { + m_keys.m_account_address.m_spend_public_key = spend_public_key; +diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h +index 2ee9545d4..0099ebfe7 100644 +--- a/src/cryptonote_basic/account.h ++++ b/src/cryptonote_basic/account.h +@@ -33,6 +33,7 @@ + #include "cryptonote_basic.h" + #include "crypto/crypto.h" + #include "serialization/keyvalue_serialization.h" ++#include "polyseed/polyseed.hpp" + + namespace cryptonote + { +@@ -45,6 +46,8 @@ namespace cryptonote + std::vector<crypto::secret_key> m_multisig_keys; + hw::device *m_device = &hw::get_device("default"); + crypto::chacha_iv m_encryption_iv; ++ crypto::secret_key m_polyseed; ++ epee::wipeable_string m_passphrase; // Only used with polyseed + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(m_account_address) +@@ -53,6 +56,8 @@ namespace cryptonote + KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_multisig_keys) + const crypto::chacha_iv default_iv{{0, 0, 0, 0, 0, 0, 0, 0}}; + KV_SERIALIZE_VAL_POD_AS_BLOB_OPT(m_encryption_iv, default_iv) ++ KV_SERIALIZE_VAL_POD_AS_BLOB_FORCE(m_polyseed) ++ KV_SERIALIZE(m_passphrase) + END_KV_SERIALIZE_MAP() + + void encrypt(const crypto::chacha_key &key); +@@ -79,6 +84,7 @@ namespace cryptonote + void create_from_device(hw::device &hwdev); + void create_from_keys(const cryptonote::account_public_address& address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey); + void create_from_viewkey(const cryptonote::account_public_address& address, const crypto::secret_key& viewkey); ++ void create_from_polyseed(const polyseed::data &polyseed, const epee::wipeable_string &passphrase); + bool make_multisig(const crypto::secret_key &view_secret_key, const crypto::secret_key &spend_secret_key, const crypto::public_key &spend_public_key, const std::vector<crypto::secret_key> &multisig_keys); + const account_keys& get_keys() const; + std::string get_public_address_str(network_type nettype) const; +diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h +index 61146a114..8e1a07110 100644 +--- a/src/cryptonote_config.h ++++ b/src/cryptonote_config.h +@@ -207,6 +207,8 @@ + + #define DNS_BLOCKLIST_LIFETIME (86400 * 8) + ++#define POLYSEED_COIN POLYSEED_MONERO ++ + //The limit is enough for the mandatory transaction content with 16 outputs (547 bytes), + //a custom tag (1 byte) and up to 32 bytes of custom data for each recipient. + // (1+32) + (1+1+16*32) + (1+16*32) = 1060 +diff --git a/src/polyseed/CMakeLists.txt b/src/polyseed/CMakeLists.txt +new file mode 100644 +index 000000000..cca4eb746 +--- /dev/null ++++ b/src/polyseed/CMakeLists.txt +@@ -0,0 +1,25 @@ ++set(polyseed_sources ++ pbkdf2.c ++ polyseed.cpp ++) ++ ++monero_find_all_headers(polyseed_private_headers "${CMAKE_CURRENT_SOURCE_DIR}") ++ ++monero_private_headers(polyseed_wrapper ++ ${polyseed_private_headers} ++) ++ ++monero_add_library(polyseed_wrapper ++ ${polyseed_sources} ++ ${polyseed_headers} ++ ${polyseed_private_headers} ++) ++ ++target_link_libraries(polyseed_wrapper ++PUBLIC ++ polyseed ++ utf8proc ++ ${SODIUM_LIBRARY} ++ PRIVATE ++ ${EXTRA_LIBRARIES} ++) +diff --git a/src/polyseed/pbkdf2.c b/src/polyseed/pbkdf2.c +new file mode 100644 +index 000000000..1c45f4708 +--- /dev/null ++++ b/src/polyseed/pbkdf2.c +@@ -0,0 +1,85 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador <tevador@gmail.com> ++// Copyright (c) 2005,2007,2009 Colin Percival ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE ++// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++// SUCH DAMAGE. ++ ++#include <string.h> ++ ++#include <sodium/crypto_auth_hmacsha256.h> ++#include <sodium/utils.h> ++ ++static inline void ++store32_be(uint8_t dst[4], uint32_t w) ++{ ++ dst[3] = (uint8_t) w; w >>= 8; ++ dst[2] = (uint8_t) w; w >>= 8; ++ dst[1] = (uint8_t) w; w >>= 8; ++ dst[0] = (uint8_t) w; ++} ++ ++void ++crypto_pbkdf2_sha256(const uint8_t* passwd, size_t passwdlen, ++ const uint8_t* salt, size_t saltlen, uint64_t c, ++ uint8_t* buf, size_t dkLen) ++{ ++ crypto_auth_hmacsha256_state Phctx, PShctx, hctx; ++ size_t i; ++ uint8_t ivec[4]; ++ uint8_t U[32]; ++ uint8_t T[32]; ++ uint64_t j; ++ int k; ++ size_t clen; ++ ++ crypto_auth_hmacsha256_init(&Phctx, passwd, passwdlen); ++ PShctx = Phctx; ++ crypto_auth_hmacsha256_update(&PShctx, salt, saltlen); ++ ++ for (i = 0; i * 32 < dkLen; i++) { ++ store32_be(ivec, (uint32_t)(i + 1)); ++ hctx = PShctx; ++ crypto_auth_hmacsha256_update(&hctx, ivec, 4); ++ crypto_auth_hmacsha256_final(&hctx, U); ++ ++ memcpy(T, U, 32); ++ for (j = 2; j <= c; j++) { ++ hctx = Phctx; ++ crypto_auth_hmacsha256_update(&hctx, U, 32); ++ crypto_auth_hmacsha256_final(&hctx, U); ++ ++ for (k = 0; k < 32; k++) { ++ T[k] ^= U[k]; ++ } ++ } ++ ++ clen = dkLen - i * 32; ++ if (clen > 32) { ++ clen = 32; ++ } ++ memcpy(&buf[i * 32], T, clen); ++ } ++ sodium_memzero((void*)&Phctx, sizeof Phctx); ++ sodium_memzero((void*)&PShctx, sizeof PShctx); ++} +\ No newline at end of file +diff --git a/src/polyseed/pbkdf2.h b/src/polyseed/pbkdf2.h +new file mode 100644 +index 000000000..f6253b9d7 +--- /dev/null ++++ b/src/polyseed/pbkdf2.h +@@ -0,0 +1,46 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador <tevador@gmail.com> ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. ++ ++#ifndef PBKDF2_H ++#define PBKDF2_H ++ ++#include <stddef.h> ++#include <stdint.h> ++ ++#ifdef __cplusplus ++extern "C" { ++#endif ++ ++void ++crypto_pbkdf2_sha256(const uint8_t* passwd, size_t passwdlen, ++ const uint8_t* salt, size_t saltlen, uint64_t c, ++ uint8_t* buf, size_t dkLen); ++ ++#ifdef __cplusplus ++} ++#endif ++ ++#endif +\ No newline at end of file +diff --git a/src/polyseed/polyseed.cpp b/src/polyseed/polyseed.cpp +new file mode 100644 +index 000000000..b26f37574 +--- /dev/null ++++ b/src/polyseed/polyseed.cpp +@@ -0,0 +1,182 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador <tevador@gmail.com> ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR OR CONTRIBUTORS BE LIABLE ++// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL ++// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS ++// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT ++// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY ++// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF ++// SUCH DAMAGE. ++ ++#include "polyseed.hpp" ++#include "pbkdf2.h" ++ ++#include <sodium/core.h> ++#include <sodium/utils.h> ++#include <sodium/randombytes.h> ++#include <utf8proc.h> ++ ++#include <cstring> ++#include <algorithm> ++#include <array> ++ ++namespace polyseed { ++ ++ inline size_t utf8_norm(const char* str, polyseed_str norm, utf8proc_option_t options) { ++ utf8proc_int32_t buffer[POLYSEED_STR_SIZE]; ++ utf8proc_ssize_t result; ++ ++ result = utf8proc_decompose(reinterpret_cast<const uint8_t*>(str), 0, buffer, POLYSEED_STR_SIZE, options); ++ if (result < 0) { ++ return POLYSEED_STR_SIZE; ++ } ++ if (result > POLYSEED_STR_SIZE - 1) { ++ return result; ++ } ++ ++ result = utf8proc_reencode(buffer, result, options); ++ ++ strcpy(norm, reinterpret_cast<const char*>(buffer)); ++ sodium_memzero(buffer, POLYSEED_STR_SIZE); ++ return result; ++ } ++ ++ static size_t utf8_nfc(const char* str, polyseed_str norm) { ++ // Note: UTF8PROC_LUMP is used here to replace the ideographic space with a regular space for Japanese phrases ++ // to allow wallets to split on ' '. ++ return utf8_norm(str, norm, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_COMPOSE | UTF8PROC_STRIPNA | UTF8PROC_LUMP)); ++ } ++ ++ static size_t utf8_nfkd(const char* str, polyseed_str norm) { ++ return utf8_norm(str, norm, (utf8proc_option_t)(UTF8PROC_NULLTERM | UTF8PROC_STABLE | UTF8PROC_DECOMPOSE | UTF8PROC_COMPAT | UTF8PROC_STRIPNA)); ++ } ++ ++ struct dependency { ++ dependency(); ++ std::vector<language> languages; ++ }; ++ ++ static dependency deps; ++ ++ dependency::dependency() { ++ if (sodium_init() == -1) { ++ throw std::runtime_error("sodium_init failed"); ++ } ++ ++ polyseed_dependency pd; ++ pd.randbytes = &randombytes_buf; ++ pd.pbkdf2_sha256 = &crypto_pbkdf2_sha256; ++ pd.memzero = &sodium_memzero; ++ pd.u8_nfc = &utf8_nfc; ++ pd.u8_nfkd = &utf8_nfkd; ++ pd.time = nullptr; ++ pd.alloc = nullptr; ++ pd.free = nullptr; ++ ++ polyseed_inject(&pd); ++ ++ for (int i = 0; i < polyseed_get_num_langs(); ++i) { ++ languages.push_back(language(polyseed_get_lang(i))); ++ } ++ } ++ ++ static language invalid_lang; ++ ++ const std::vector<language>& get_langs() { ++ return deps.languages; ++ } ++ ++ const language& get_lang_by_name(const std::string& name) { ++ for (auto& lang : deps.languages) { ++ if (name == lang.name_en()) { ++ return lang; ++ } ++ if (name == lang.name()) { ++ return lang; ++ } ++ } ++ return invalid_lang; ++ } ++ ++ inline void data::check_init() const { ++ if (valid()) { ++ throw std::runtime_error("already initialized"); ++ } ++ } ++ ++ static std::array<const char*, 8> error_desc = { ++ "Success", ++ "Wrong number of words in the phrase", ++ "Unknown language or unsupported words", ++ "Checksum mismatch", ++ "Unsupported seed features", ++ "Invalid seed format", ++ "Memory allocation failure", ++ "Unicode normalization failed" ++ }; ++ ++ static error get_error(polyseed_status status) { ++ if (status > 0 && status < sizeof(error_desc) / sizeof(const char*)) { ++ return error(error_desc[(int)status], status); ++ } ++ return error("Unknown error", status); ++ } ++ ++ void data::create(feature_type features) { ++ check_init(); ++ auto status = polyseed_create(features, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ } ++ ++ void data::split(const language& lang, polyseed_phrase& words) { ++ check_init(); ++ if (!lang.valid()) { ++ throw std::runtime_error("invalid language"); ++ } ++ } ++ ++ void data::load(polyseed_storage storage) { ++ check_init(); ++ auto status = polyseed_load(storage, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ } ++ ++ void data::load(const crypto::secret_key &key) { ++ polyseed_storage d; ++ memcpy(&d, &key.data, 32); ++ auto status = polyseed_load(d, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ } ++ ++ language data::decode(const char* phrase) { ++ check_init(); ++ const polyseed_lang* lang; ++ auto status = polyseed_decode(phrase, m_coin, &lang, &m_data); ++ if (status != POLYSEED_OK) { ++ throw get_error(status); ++ } ++ return language(lang); ++ } ++} +diff --git a/src/polyseed/polyseed.hpp b/src/polyseed/polyseed.hpp +new file mode 100644 +index 000000000..2c8c777a7 +--- /dev/null ++++ b/src/polyseed/polyseed.hpp +@@ -0,0 +1,167 @@ ++// Copyright (c) 2023, The Monero Project ++// Copyright (c) 2021, tevador <tevador@gmail.com> ++// ++// 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. ++// ++// THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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. ++ ++#ifndef POLYSEED_HPP ++#define POLYSEED_HPP ++ ++#include <polyseed/include/polyseed.h> ++#include <polyseed/src/lang.h> ++#include <vector> ++#include <stdexcept> ++#include <string> ++#include "crypto/crypto.h" ++ ++namespace polyseed { ++ ++ class data; ++ ++ class language { ++ public: ++ language() : m_lang(nullptr) {} ++ language(const language&) = default; ++ language(const polyseed_lang* lang) : m_lang(lang) {} ++ const char* name() const { ++ return polyseed_get_lang_name(m_lang); ++ } ++ const char* name_en() const { ++ return polyseed_get_lang_name_en(m_lang); ++ } ++ const char* separator() const { ++ return m_lang->separator; ++ } ++ bool valid() const { ++ return m_lang != nullptr; ++ } ++ ++ const polyseed_lang* m_lang; ++ private: ++ ++ friend class data; ++ }; ++ ++ const std::vector<language>& get_langs(); ++ const language& get_lang_by_name(const std::string& name); ++ ++ class error : public std::runtime_error { ++ public: ++ error(const char* msg, polyseed_status status) ++ : std::runtime_error(msg), m_status(status) ++ { ++ } ++ polyseed_status status() const { ++ return m_status; ++ } ++ private: ++ polyseed_status m_status; ++ }; ++ ++ using feature_type = unsigned int; ++ ++ inline int enable_features(feature_type features) { ++ return polyseed_enable_features(features); ++ } ++ ++ class data { ++ public: ++ data(const data&) = delete; ++ data(polyseed_coin coin) : m_data(nullptr), m_coin(coin) {} ++ ~data() { ++ polyseed_free(m_data); ++ } ++ ++ void create(feature_type features); ++ ++ void load(polyseed_storage storage); ++ ++ void load(const crypto::secret_key &key); ++ ++ language decode(const char* phrase); ++ ++ template<class str_type> ++ void encode(const language& lang, str_type& str) const { ++ check_valid(); ++ if (!lang.valid()) { ++ throw std::runtime_error("invalid language"); ++ } ++ str.resize(POLYSEED_STR_SIZE); ++ auto size = polyseed_encode(m_data, lang.m_lang, m_coin, &str[0]); ++ str.resize(size); ++ } ++ ++ void split(const language& lang, polyseed_phrase& words); ++ ++ void save(polyseed_storage storage) const { ++ check_valid(); ++ polyseed_store(m_data, storage); ++ } ++ ++ void save(void *storage) const { ++ check_valid(); ++ polyseed_store(m_data, (uint8_t*)storage); ++ } ++ ++ void crypt(const char* password) { ++ check_valid(); ++ polyseed_crypt(m_data, password); ++ } ++ ++ void keygen(void* ptr, size_t key_size) const { ++ check_valid(); ++ polyseed_keygen(m_data, m_coin, key_size, (uint8_t*)ptr); ++ } ++ ++ bool valid() const { ++ return m_data != nullptr; ++ } ++ ++ bool encrypted() const { ++ check_valid(); ++ return polyseed_is_encrypted(m_data); ++ } ++ ++ uint64_t birthday() const { ++ check_valid(); ++ return polyseed_get_birthday(m_data); ++ } ++ ++ bool has_feature(feature_type feature) const { ++ check_valid(); ++ return polyseed_get_feature(m_data, feature) != 0; ++ } ++ private: ++ void check_valid() const { ++ if (m_data == nullptr) { ++ throw std::runtime_error("invalid object"); ++ } ++ } ++ void check_init() const; ++ ++ polyseed_data* m_data; ++ polyseed_coin m_coin; ++ }; ++} ++ ++#endif //POLYSEED_HPP +\ No newline at end of file +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 8d7364cba..472f05016 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -690,6 +690,28 @@ bool WalletImpl::recoverFromDevice(const std::string &path, const std::string &p + return true; + } + ++bool WalletImpl::createFromPolyseed(const std::string &path, const std::string &password, const std::string &seed, ++ const std::string &passphrase, bool newWallet, uint64_t restoreHeight) ++{ ++ clearStatus(); ++ m_recoveringFromSeed = !newWallet; ++ m_recoveringFromDevice = false; ++ ++ polyseed::data polyseed(POLYSEED_COIN); ++ ++ try { ++ auto lang = polyseed.decode(seed.data()); ++ m_wallet->set_seed_language(lang.name()); ++ m_wallet->generate(path, password, polyseed, passphrase, !newWallet); ++ } ++ catch (const std::exception &e) { ++ setStatusError(e.what()); ++ return false; ++ } ++ ++ return true; ++} ++ + Wallet::Device WalletImpl::getDeviceType() const + { + return static_cast<Wallet::Device>(m_wallet->get_device_type()); +@@ -798,6 +820,55 @@ std::string WalletImpl::seed(const std::string& seed_offset) const + return std::string(seed.data(), seed.size()); // TODO + } + ++bool WalletImpl::getPolyseed(std::string &seed_words, std::string &passphrase) const ++{ ++ epee::wipeable_string seed_words_epee(seed_words.c_str(), seed_words.size()); ++ epee::wipeable_string passphrase_epee(passphrase.c_str(), passphrase.size()); ++ clearStatus(); ++ ++ if (!m_wallet) { ++ return false; ++ } ++ ++ bool result = m_wallet->get_polyseed(seed_words_epee, passphrase_epee); ++ ++ seed_words.assign(seed_words_epee.data(), seed_words_epee.size()); ++ passphrase.assign(passphrase_epee.data(), passphrase_epee.size()); ++ ++ return result; ++} ++ ++std::vector<std::pair<std::string, std::string>> Wallet::getPolyseedLanguages() ++{ ++ std::vector<std::pair<std::string, std::string>> languages; ++ ++ auto langs = polyseed::get_langs(); ++ for (const auto &lang : langs) { ++ languages.emplace_back(std::pair<std::string, std::string>(lang.name_en(), lang.name())); ++ } ++ ++ return languages; ++} ++ ++bool Wallet::createPolyseed(std::string &seed_words, std::string &err, const std::string &language) ++{ ++ epee::wipeable_string seed_words_epee(seed_words.c_str(), seed_words.size()); ++ ++ try { ++ polyseed::data polyseed(POLYSEED_COIN); ++ polyseed.create(0); ++ polyseed.encode(polyseed::get_lang_by_name(language), seed_words_epee); ++ ++ seed_words.assign(seed_words_epee.data(), seed_words_epee.size()); ++ } ++ catch (const std::exception &e) { ++ err = e.what(); ++ return false; ++ } ++ ++ return true; ++} ++ + std::string WalletImpl::getSeedLanguage() const + { + return m_wallet->get_seed_language(); +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index ec2d7e9b3..787215ab3 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -79,9 +79,19 @@ public: + bool recoverFromDevice(const std::string &path, + const std::string &password, + const std::string &device_name); ++ ++ bool createFromPolyseed(const std::string &path, ++ const std::string &password, ++ const std::string &seed, ++ const std::string &passphrase = "", ++ bool newWallet = true, ++ uint64_t restoreHeight = 0); ++ + Device getDeviceType() const override; + bool close(bool store = true); + std::string seed(const std::string& seed_offset = "") const override; ++ bool getPolyseed(std::string &seed_words, std::string &passphrase) const override; ++ + std::string getSeedLanguage() const override; + void setSeedLanguage(const std::string &arg) override; + // void setListener(Listener *) {} +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 71991df0d..9ea753083 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -700,6 +700,10 @@ struct Wallet + static void warning(const std::string &category, const std::string &str); + static void error(const std::string &category, const std::string &str); + ++ virtual bool getPolyseed(std::string &seed, std::string &passphrase) const = 0; ++ static bool createPolyseed(std::string &seed_words, std::string &err, const std::string &language = "English"); ++ static std::vector<std::pair<std::string, std::string>> getPolyseedLanguages(); ++ + /** + * @brief StartRefresh - Start/resume refresh thread (refresh every 10 seconds) + */ +@@ -1256,6 +1260,27 @@ struct WalletManager + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) = 0; + ++ /*! ++ * \brief creates a wallet from a polyseed mnemonic phrase ++ * \param path Name of the wallet file to be created ++ * \param password Password of wallet file ++ * \param nettype Network type ++ * \param mnemonic Polyseed mnemonic ++ * \param passphrase Optional seed offset passphrase ++ * \param newWallet Whether it is a new wallet ++ * \param restoreHeight Override the embedded restore height ++ * \param kdf_rounds Number of rounds for key derivation function ++ * @return ++ */ ++ virtual Wallet * createWalletFromPolyseed(const std::string &path, ++ const std::string &password, ++ NetworkType nettype, ++ const std::string &mnemonic, ++ const std::string &passphrase = "", ++ bool newWallet = true, ++ uint64_t restore_height = 0, ++ uint64_t kdf_rounds = 1) = 0; ++ + /*! + * \brief Closes wallet. In case operation succeeded, wallet object deleted. in case operation failed, wallet object not deleted + * \param wallet previously opened / created wallet instance +diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp +index e81b8f83a..c79fe25d6 100644 +--- a/src/wallet/api/wallet_manager.cpp ++++ b/src/wallet/api/wallet_manager.cpp +@@ -156,6 +156,15 @@ Wallet *WalletManagerImpl::createWalletFromDevice(const std::string &path, + return wallet; + } + ++Wallet *WalletManagerImpl::createWalletFromPolyseed(const std::string &path, const std::string &password, NetworkType nettype, ++ const std::string &mnemonic, const std::string &passphrase, ++ bool newWallet, uint64_t restoreHeight, uint64_t kdf_rounds) ++{ ++ WalletImpl * wallet = new WalletImpl(nettype, kdf_rounds); ++ wallet->createFromPolyseed(path, password, mnemonic, passphrase, newWallet, restoreHeight); ++ return wallet; ++} ++ + bool WalletManagerImpl::closeWallet(Wallet *wallet, bool store) + { + WalletImpl * wallet_ = dynamic_cast<WalletImpl*>(wallet); +diff --git a/src/wallet/api/wallet_manager.h b/src/wallet/api/wallet_manager.h +index a223e1df9..28fcd36c9 100644 +--- a/src/wallet/api/wallet_manager.h ++++ b/src/wallet/api/wallet_manager.h +@@ -75,6 +75,16 @@ public: + const std::string &subaddressLookahead = "", + uint64_t kdf_rounds = 1, + WalletListener * listener = nullptr) override; ++ ++ virtual Wallet * createWalletFromPolyseed(const std::string &path, ++ const std::string &password, ++ NetworkType nettype, ++ const std::string &mnemonic, ++ const std::string &passphrase, ++ bool newWallet = true, ++ uint64_t restore_height = 0, ++ uint64_t kdf_rounds = 1) override; ++ + virtual bool closeWallet(Wallet *wallet, bool store = true) override; + bool walletExists(const std::string &path) override; + bool verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key, uint64_t kdf_rounds = 1) const override; +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index f34b10988..e4e02c782 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -92,6 +92,7 @@ using namespace epee; + #include "device/device_cold.hpp" + #include "device_trezor/device_trezor.hpp" + #include "net/socks_connect.h" ++#include "polyseed/include/polyseed.h" + + extern "C" + { +@@ -1260,7 +1261,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std + m_enable_multisig(false), + m_pool_info_query_time(0), + m_has_ever_refreshed_from_node(false), +- m_allow_mismatched_daemon_version(false) ++ m_allow_mismatched_daemon_version(false), ++ m_polyseed(false) + { + set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); + } +@@ -1438,10 +1440,25 @@ bool wallet2::get_seed(epee::wipeable_string& electrum_words, const epee::wipeab + key = cryptonote::encrypt_key(key, passphrase); + if (!crypto::ElectrumWords::bytes_to_words(key, electrum_words, seed_language)) + { +- std::cout << "Failed to create seed from key for language: " << seed_language << std::endl; ++ std::cout << "Failed to create seed from key for language: " << seed_language << ", falling back to English." << std::endl; ++ crypto::ElectrumWords::bytes_to_words(key, electrum_words, "English"); ++ } ++ ++ return true; ++} ++//---------------------------------------------------------------------------------------------------- ++bool wallet2::get_polyseed(epee::wipeable_string& polyseed, epee::wipeable_string& passphrase) const ++{ ++ if (!m_polyseed) { + return false; + } + ++ polyseed::data data(POLYSEED_COIN); ++ data.load(get_account().get_keys().m_polyseed); ++ data.encode(polyseed::get_lang_by_name(seed_language), polyseed); ++ ++ passphrase = get_account().get_keys().m_passphrase; ++ + return true; + } + //---------------------------------------------------------------------------------------------------- +@@ -4629,6 +4646,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: + value2.SetInt(m_enable_multisig ? 1 : 0); + json.AddMember("enable_multisig", value2, json.GetAllocator()); + ++ value2.SetInt(m_polyseed ? 1 : 0); ++ json.AddMember("polyseed", value2, json.GetAllocator()); ++ + // Serialize the JSON object + rapidjson::StringBuffer buffer; + rapidjson::Writer<rapidjson::StringBuffer> writer(buffer); +@@ -4776,6 +4796,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + m_credits_target = 0; + m_enable_multisig = false; + m_allow_mismatched_daemon_version = false; ++ m_polyseed = false; + } + else if(json.IsObject()) + { +@@ -5012,6 +5033,8 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + m_credits_target = field_credits_target; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); + m_enable_multisig = field_enable_multisig; ++ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, polyseed, int, Int, false, false); ++ m_polyseed = field_polyseed; + } + else + { +@@ -5284,6 +5307,48 @@ void wallet2::init_type(hw::device::device_type device_type) + m_key_device_type = device_type; + } + ++/*! ++ * \brief Generates a polyseed wallet or restores one. ++ * \param wallet_ Name of wallet file ++ * \param password Password of wallet file ++ * \param passphrase Seed offset passphrase ++ * \param recover Whether it is a restore ++ * \param seed_words If it is a restore, the polyseed ++ * \param create_address_file Whether to create an address file ++ * \return The secret key of the generated wallet ++ */ ++void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, ++ const polyseed::data &seed, const epee::wipeable_string& passphrase, bool recover, uint64_t restoreHeight, bool create_address_file) ++{ ++ clear(); ++ prepare_file_names(wallet_); ++ ++ if (!wallet_.empty()) { ++ boost::system::error_code ignored_ec; ++ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_wallet_file, ignored_ec), error::file_exists, m_wallet_file); ++ THROW_WALLET_EXCEPTION_IF(boost::filesystem::exists(m_keys_file, ignored_ec), error::file_exists, m_keys_file); ++ } ++ ++ m_account.create_from_polyseed(seed, passphrase); ++ ++ init_type(hw::device::device_type::SOFTWARE); ++ m_polyseed = true; ++ setup_keys(password); ++ ++ if (recover) { ++ m_refresh_from_block_height = estimate_blockchain_height(restoreHeight > 0 ? restoreHeight : seed.birthday()); ++ } else { ++ m_refresh_from_block_height = estimate_blockchain_height(); ++ } ++ ++ create_keys_file(wallet_, false, password, m_nettype != MAINNET || create_address_file); ++ ++ setup_new_blockchain(); ++ ++ if (!wallet_.empty()) ++ store(); ++} ++ + /*! + * \brief Generates a wallet or restores one. Assumes the multisig setup + * has already completed for the provided multisig info. +@@ -5411,7 +5476,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip + return retval; + } + +- uint64_t wallet2::estimate_blockchain_height() ++ uint64_t wallet2::estimate_blockchain_height(uint64_t time) + { + // -1 month for fluctuations in block time and machine date/time setup. + // avg seconds per block +@@ -5435,7 +5500,7 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip + // the daemon is currently syncing. + // If we use the approximate height we subtract one month as + // a safety margin. +- height = get_approximate_blockchain_height(); ++ height = get_approximate_blockchain_height(time); + uint64_t target_height = get_daemon_blockchain_target_height(err); + if (err.empty()) { + if (target_height < height) +@@ -13135,7 +13200,7 @@ uint64_t wallet2::get_daemon_blockchain_target_height(string &err) + return target_height; + } + +-uint64_t wallet2::get_approximate_blockchain_height() const ++uint64_t wallet2::get_approximate_blockchain_height(uint64_t t) const + { + // time of v2 fork + const time_t fork_time = m_nettype == TESTNET ? 1448285909 : m_nettype == STAGENET ? 1520937818 : 1458748658; +@@ -13144,7 +13209,7 @@ uint64_t wallet2::get_approximate_blockchain_height() const + // avg seconds per block + const int seconds_per_block = DIFFICULTY_TARGET_V2; + // Calculated blockchain height +- uint64_t approx_blockchain_height = fork_block + (time(NULL) - fork_time)/seconds_per_block; ++ uint64_t approx_blockchain_height = fork_block + ((t > 0 ? t : time(NULL)) - fork_time)/seconds_per_block; + // testnet and stagenet got some huge rollbacks, so the estimation is way off + static const uint64_t approximate_rolled_back_blocks = m_nettype == TESTNET ? 342100 : 30000; + if ((m_nettype == TESTNET || m_nettype == STAGENET) && approx_blockchain_height > approximate_rolled_back_blocks) +@@ -14862,6 +14927,21 @@ bool wallet2::parse_uri(const std::string &uri, std::string &address, std::strin + //---------------------------------------------------------------------------------------------------- + uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day) + { ++ std::tm date = { 0, 0, 0, 0, 0, 0, 0, 0 }; ++ date.tm_year = year - 1900; ++ date.tm_mon = month - 1; ++ date.tm_mday = day; ++ if (date.tm_mon < 0 || 11 < date.tm_mon || date.tm_mday < 1 || 31 < date.tm_mday) ++ { ++ throw std::runtime_error("month or day out of range"); ++ } ++ ++ uint64_t timestamp_target = std::mktime(&date); ++ ++ return get_blockchain_height_by_timestamp(timestamp_target); ++} ++ ++uint64_t wallet2::get_blockchain_height_by_timestamp(uint64_t timestamp_target) { + uint32_t version; + if (!check_connection(&version)) + { +@@ -14871,15 +14951,7 @@ uint64_t wallet2::get_blockchain_height_by_date(uint16_t year, uint8_t month, ui + { + throw std::runtime_error("this function requires RPC version 1.6 or higher"); + } +- std::tm date = { 0, 0, 0, 0, 0, 0, 0, 0 }; +- date.tm_year = year - 1900; +- date.tm_mon = month - 1; +- date.tm_mday = day; +- if (date.tm_mon < 0 || 11 < date.tm_mon || date.tm_mday < 1 || 31 < date.tm_mday) +- { +- throw std::runtime_error("month or day out of range"); +- } +- uint64_t timestamp_target = std::mktime(&date); ++ + std::string err; + uint64_t height_min = 0; + uint64_t height_max = get_daemon_blockchain_height(err) - 1; +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index 3144a8fd3..b540eff6b 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -72,6 +72,7 @@ + #include "message_store.h" + #include "wallet_light_rpc.h" + #include "wallet_rpc_helpers.h" ++#include "polyseed/polyseed.hpp" + + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2" +@@ -854,6 +855,20 @@ private: + void generate(const std::string& wallet_, const epee::wipeable_string& password, + const epee::wipeable_string& multisig_data, bool create_address_file = false); + ++ /*! ++ * \brief Generates a wallet from a polyseed. ++ * @param wallet_ Name of wallet file ++ * @param password Password of wallet file ++ * @param seed Polyseed data ++ * @param passphrase Optional seed offset passphrase ++ * @param recover Whether it is a restore ++ * @param restoreHeight Override the embedded restore height ++ * @param create_address_file Whether to create an address file ++ */ ++ void generate(const std::string& wallet_, const epee::wipeable_string& password, ++ const polyseed::data &seed, const epee::wipeable_string& passphrase = "", ++ bool recover = false, uint64_t restoreHeight = 0, bool create_address_file = false); ++ + /*! + * \brief Generates a wallet or restores one. + * \param wallet_ Name of wallet file +@@ -1018,6 +1033,15 @@ private: + bool is_deterministic() const; + bool get_seed(epee::wipeable_string& electrum_words, const epee::wipeable_string &passphrase = epee::wipeable_string()) const; + ++ /*! ++ * \brief get_polyseed Gets the polyseed (if available) and passphrase (if set) needed to recover the wallet. ++ * @param seed Polyseed mnemonic phrase ++ * @param passphrase Seed offset passphrase that was used to restore the wallet ++ * @return Returns true if the wallet has a polyseed. ++ * Note: both the mnemonic phrase and the passphrase are needed to recover the wallet ++ */ ++ bool get_polyseed(epee::wipeable_string& seed, epee::wipeable_string &passphrase) const; ++ + /*! + * \brief Checks if light wallet. A light wallet sends view key to a server where the blockchain is scanned. + */ +@@ -1466,8 +1490,8 @@ private: + /*! + * \brief Calculates the approximate blockchain height from current date/time. + */ +- uint64_t get_approximate_blockchain_height() const; +- uint64_t estimate_blockchain_height(); ++ uint64_t get_approximate_blockchain_height(uint64_t time = 0) const; ++ uint64_t estimate_blockchain_height(uint64_t time = 0); + std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool allow_rct); + std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f); + std::vector<size_t> select_available_unmixable_outputs(); +@@ -1559,6 +1583,7 @@ private: + bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error); + + uint64_t get_blockchain_height_by_date(uint16_t year, uint8_t month, uint8_t day); // 1<=month<=12, 1<=day<=31 ++ uint64_t get_blockchain_height_by_timestamp(uint64_t timestamp); + + bool is_synced(); + +@@ -1874,6 +1899,7 @@ private: + std::string seed_language; /*!< Language of the mnemonics (seed). */ + bool is_old_file_format; /*!< Whether the wallet file is of an old file format */ + bool m_watch_only; /*!< no spend key */ ++ bool m_polyseed; + bool m_multisig; /*!< if > 1 spend secret key will not match spend public key */ + uint32_t m_multisig_threshold; + std::vector<crypto::public_key> m_multisig_signers; diff --git a/patches/0001-background-sync.patch b/patches/0001-background-sync.patch new file mode 100644 index 0000000..63e95cb --- /dev/null +++ b/patches/0001-background-sync.patch @@ -0,0 +1,4172 @@ +From c7da75fee9bea42a6a741905cf6212db5ea8c54b Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto <cyjan@mrcyjanek.net> +Date: Tue, 12 Mar 2024 10:02:23 +0100 +Subject: [PATCH] PATCH: background-sync + +--- + src/cryptonote_basic/account.cpp | 11 + + src/cryptonote_basic/account.h | 1 + + src/cryptonote_config.h | 3 + + src/simplewallet/simplewallet.cpp | 205 +++- + src/simplewallet/simplewallet.h | 1 + + src/wallet/api/wallet.cpp | 213 ++++- + src/wallet/api/wallet.h | 12 + + src/wallet/api/wallet2_api.h | 42 + + src/wallet/wallet2.cpp | 948 ++++++++++++++++++- + src/wallet/wallet2.h | 142 ++- + src/wallet/wallet_errors.h | 39 + + src/wallet/wallet_rpc_server.cpp | 162 ++++ + src/wallet/wallet_rpc_server.h | 6 + + src/wallet/wallet_rpc_server_commands_defs.h | 64 ++ + src/wallet/wallet_rpc_server_error_codes.h | 2 + + tests/functional_tests/transfer.py | 401 +++++++- + tests/functional_tests/util_resources.py | 25 + + tests/functional_tests/wallet.py | 43 +- + tests/unit_tests/wipeable_string.cpp | 12 + + utils/python-rpc/framework/wallet.py | 35 + + 20 files changed, 2281 insertions(+), 86 deletions(-) + +diff --git a/src/cryptonote_basic/account.cpp b/src/cryptonote_basic/account.cpp +index 4931c3740..2d556f285 100644 +--- a/src/cryptonote_basic/account.cpp ++++ b/src/cryptonote_basic/account.cpp +@@ -158,6 +158,17 @@ DISABLE_VS_WARNINGS(4244 4345) + m_keys.m_passphrase.wipe(); + } + //----------------------------------------------------------------- ++ void account_base::set_spend_key(const crypto::secret_key& spend_secret_key) ++ { ++ // make sure derived spend public key matches saved public spend key ++ crypto::public_key spend_public_key; ++ crypto::secret_key_to_public_key(spend_secret_key, spend_public_key); ++ CHECK_AND_ASSERT_THROW_MES(m_keys.m_account_address.m_spend_public_key == spend_public_key, ++ "Unexpected derived public spend key"); ++ ++ m_keys.m_spend_secret_key = spend_secret_key; ++ } ++ //----------------------------------------------------------------- + crypto::secret_key account_base::generate(const crypto::secret_key& recovery_key, bool recover, bool two_random) + { + crypto::secret_key first = generate_keys(m_keys.m_account_address.m_spend_public_key, m_keys.m_spend_secret_key, recovery_key, recover); +diff --git a/src/cryptonote_basic/account.h b/src/cryptonote_basic/account.h +index 0099ebfe7..1f76febce 100644 +--- a/src/cryptonote_basic/account.h ++++ b/src/cryptonote_basic/account.h +@@ -101,6 +101,7 @@ namespace cryptonote + bool store(const std::string& file_path); + + void forget_spend_key(); ++ void set_spend_key(const crypto::secret_key& spend_secret_key); + const std::vector<crypto::secret_key> &get_multisig_keys() const { return m_keys.m_multisig_keys; } + + void encrypt_keys(const crypto::chacha_key &key) { m_keys.encrypt(key); } +diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h +index 8e1a07110..cf50b2adc 100644 +--- a/src/cryptonote_config.h ++++ b/src/cryptonote_config.h +@@ -233,6 +233,7 @@ namespace config + } }; // Bender's nightmare + std::string const GENESIS_TX = "013c01ff0001ffffffffffff03029b2e4c0281c0b02e7c53291a94d1d0cbff8883f8024f5142ee494ffbbd08807121017767aafcde9be00dcfd098715ebcf7f410daebc582fda69d24a28e9d0bc890d1"; + uint32_t const GENESIS_NONCE = 10000; ++ std::string const BACKGROUND_WALLET_SUFFIX = ".background"; + + // Hash domain separators + const char HASH_KEY_BULLETPROOF_EXPONENT[] = "bulletproof"; +@@ -243,6 +244,8 @@ namespace config + const unsigned char HASH_KEY_ENCRYPTED_PAYMENT_ID = 0x8d; + const unsigned char HASH_KEY_WALLET = 0x8c; + const unsigned char HASH_KEY_WALLET_CACHE = 0x8d; ++ const unsigned char HASH_KEY_BACKGROUND_CACHE = 0x8e; ++ const unsigned char HASH_KEY_BACKGROUND_KEYS_FILE = 0x8f; + const unsigned char HASH_KEY_RPC_PAYMENT_NONCE = 0x58; + const unsigned char HASH_KEY_MEMORY = 'k'; + const unsigned char HASH_KEY_MULTISIG[] = {'M', 'u', 'l', 't' , 'i', 's', 'i', 'g', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; +diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp +index f2eaf6b13..341b0e448 100644 +--- a/src/simplewallet/simplewallet.cpp ++++ b/src/simplewallet/simplewallet.cpp +@@ -155,6 +155,17 @@ typedef cryptonote::simple_wallet sw; + } \ + } while(0) + ++#define CHECK_IF_BACKGROUND_SYNCING(msg) \ ++ do \ ++ { \ ++ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) \ ++ { \ ++ std::string type = m_wallet->is_background_wallet() ? "background wallet" : "background syncing wallet"; \ ++ fail_msg_writer() << boost::format(tr("%s %s")) % type % msg; \ ++ return false; \ ++ } \ ++ } while (0) ++ + enum TransferType { + Transfer, + TransferLocked, +@@ -332,7 +343,7 @@ namespace + auto pwd_container = tools::password_container::prompt(verify, prompt); + if (!pwd_container) + { +- tools::fail_msg_writer() << sw::tr("failed to read wallet password"); ++ tools::fail_msg_writer() << sw::tr("failed to read password"); + } + return pwd_container; + } +@@ -342,6 +353,11 @@ namespace + return password_prompter(verify ? sw::tr("Enter a new password for the wallet") : sw::tr("Wallet password"), verify); + } + ++ boost::optional<tools::password_container> background_sync_cache_password_prompter(bool verify) ++ { ++ return password_prompter(verify ? sw::tr("Enter a custom password for the background sync cache") : sw::tr("Background sync cache password"), verify); ++ } ++ + inline std::string interpret_rpc_response(bool ok, const std::string& status) + { + std::string err; +@@ -459,6 +475,41 @@ namespace + return "invalid"; + } + ++ const struct ++ { ++ const char *name; ++ tools::wallet2::BackgroundSyncType background_sync_type; ++ } background_sync_type_names[] = ++ { ++ { "off", tools::wallet2::BackgroundSyncOff }, ++ { "reuse-wallet-password", tools::wallet2::BackgroundSyncReusePassword }, ++ { "custom-background-password", tools::wallet2::BackgroundSyncCustomPassword }, ++ }; ++ ++ bool parse_background_sync_type(const std::string &s, tools::wallet2::BackgroundSyncType &background_sync_type) ++ { ++ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n) ++ { ++ if (s == background_sync_type_names[n].name) ++ { ++ background_sync_type = background_sync_type_names[n].background_sync_type; ++ return true; ++ } ++ } ++ fail_msg_writer() << cryptonote::simple_wallet::tr("failed to parse background sync type"); ++ return false; ++ } ++ ++ std::string get_background_sync_type_name(tools::wallet2::BackgroundSyncType type) ++ { ++ for (size_t n = 0; n < sizeof(background_sync_type_names) / sizeof(background_sync_type_names[0]); ++n) ++ { ++ if (type == background_sync_type_names[n].background_sync_type) ++ return background_sync_type_names[n].name; ++ } ++ return "invalid"; ++ } ++ + std::string get_version_string(uint32_t version) + { + return boost::lexical_cast<std::string>(version >> 16) + "." + boost::lexical_cast<std::string>(version & 0xffff); +@@ -812,6 +863,7 @@ bool simple_wallet::spendkey(const std::vector<std::string> &args/* = std::vecto + fail_msg_writer() << tr("wallet is watch-only and has no spend key"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("has no spend key"); + // don't log + PAUSE_READLINE(); + if (m_wallet->key_on_device()) { +@@ -843,6 +895,7 @@ bool simple_wallet::print_seed(bool encrypted) + fail_msg_writer() << tr("wallet is watch-only and has no seed"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("has no seed"); + + multisig = m_wallet->multisig(&ready); + if (multisig) +@@ -920,6 +973,7 @@ bool simple_wallet::seed_set_language(const std::vector<std::string> &args/* = s + fail_msg_writer() << tr("wallet is watch-only and has no seed"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("has no seed"); + + epee::wipeable_string password; + { +@@ -1066,6 +1120,7 @@ bool simple_wallet::prepare_multisig_main(const std::vector<std::string> &args, + fail_msg_writer() << tr("wallet is watch-only and cannot be made multisig"); + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING("cannot be made multisig"); + + if(m_wallet->get_num_transfer_details()) + { +@@ -2202,6 +2257,7 @@ bool simple_wallet::save_known_rings(const std::vector<std::string> &args) + + bool simple_wallet::freeze_thaw(const std::vector<std::string> &args, bool freeze) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot freeze/thaw"); + if (args.empty()) + { + fail_msg_writer() << boost::format(tr("usage: %s <key_image>|<pubkey>")) % (freeze ? "freeze" : "thaw"); +@@ -2241,6 +2297,7 @@ bool simple_wallet::thaw(const std::vector<std::string> &args) + + bool simple_wallet::frozen(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot see frozen key images"); + if (args.empty()) + { + size_t ntd = m_wallet->get_num_transfer_details(); +@@ -3012,6 +3069,56 @@ bool simple_wallet::set_track_uses(const std::vector<std::string> &args/* = std: + return true; + } + ++bool simple_wallet::setup_background_sync(const std::vector<std::string> &args/* = std::vector<std::string>()*/) ++{ ++ if (m_wallet->multisig()) ++ { ++ fail_msg_writer() << tr("background sync not implemented for multisig wallet"); ++ return true; ++ } ++ if (m_wallet->watch_only()) ++ { ++ fail_msg_writer() << tr("background sync not implemented for watch only wallet"); ++ return true; ++ } ++ if (m_wallet->key_on_device()) ++ { ++ fail_msg_writer() << tr("command not supported by HW wallet"); ++ return true; ++ } ++ ++ const auto pwd_container = get_and_verify_password(); ++ if (pwd_container) ++ { ++ tools::wallet2::BackgroundSyncType background_sync_type; ++ if (!parse_background_sync_type(args[1], background_sync_type)) ++ { ++ fail_msg_writer() << tr("invalid option"); ++ return true; ++ } ++ ++ try ++ { ++ boost::optional<epee::wipeable_string> background_cache_password = boost::none; ++ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword) ++ { ++ const auto background_pwd_container = background_sync_cache_password_prompter(true); ++ if (!background_pwd_container) ++ return true; ++ background_cache_password = background_pwd_container->password(); ++ } ++ ++ LOCK_IDLE_SCOPE(); ++ m_wallet->setup_background_sync(background_sync_type, pwd_container->password(), background_cache_password); ++ } ++ catch (const std::exception &e) ++ { ++ fail_msg_writer() << tr("Error setting background sync type: ") << e.what(); ++ } ++ } ++ return true; ++} ++ + bool simple_wallet::set_show_wallet_name_when_locked(const std::vector<std::string> &args/* = std::vector<std::string>()*/) + { + const auto pwd_container = get_and_verify_password(); +@@ -3244,6 +3351,7 @@ bool simple_wallet::apropos(const std::vector<std::string> &args) + + bool simple_wallet::scan_tx(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot scan tx"); + if (args.empty()) + { + PRINT_USAGE(USAGE_SCAN_TX); +@@ -3473,6 +3581,8 @@ simple_wallet::simple_wallet() + " Ignore outputs of amount below this threshold when spending.\n " + "track-uses <1|0>\n " + " Whether to keep track of owned outputs uses.\n " ++ "background-sync <off|reuse-wallet-password|custom-background-password>\n " ++ " Set this to enable scanning in the background with just the view key while the wallet is locked.\n " + "setup-background-mining <1|0>\n " + " Whether to enable background mining. Set this to support the network and to get a chance to receive new monero.\n " + "device-name <device_name[:device_spec]>\n " +@@ -3891,6 +4001,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) + success_msg_writer() << "ignore-outputs-above = " << cryptonote::print_money(m_wallet->ignore_outputs_above()); + success_msg_writer() << "ignore-outputs-below = " << cryptonote::print_money(m_wallet->ignore_outputs_below()); + success_msg_writer() << "track-uses = " << m_wallet->track_uses(); ++ success_msg_writer() << "background-sync = " << get_background_sync_type_name(m_wallet->background_sync_type()); + success_msg_writer() << "setup-background-mining = " << setup_background_mining_string; + success_msg_writer() << "device-name = " << m_wallet->device_name(); + success_msg_writer() << "export-format = " << (m_wallet->export_format() == tools::wallet2::ExportFormat::Ascii ? "ascii" : "binary"); +@@ -3909,6 +4020,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) + } + else + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot change wallet settings"); + + #define CHECK_SIMPLE_VARIABLE(name, f, help) do \ + if (args[0] == name) { \ +@@ -3962,6 +4074,7 @@ bool simple_wallet::set_variable(const std::vector<std::string> &args) + CHECK_SIMPLE_VARIABLE("ignore-outputs-above", set_ignore_outputs_above, tr("amount")); + CHECK_SIMPLE_VARIABLE("ignore-outputs-below", set_ignore_outputs_below, tr("amount")); + CHECK_SIMPLE_VARIABLE("track-uses", set_track_uses, tr("0 or 1")); ++ CHECK_SIMPLE_VARIABLE("background-sync", setup_background_sync, tr("off (default); reuse-wallet-password (reuse the wallet password to encrypt the background cache); custom-background-password (use a custom background password to encrypt the background cache)")); + CHECK_SIMPLE_VARIABLE("show-wallet-name-when-locked", set_show_wallet_name_when_locked, tr("1 or 0")); + CHECK_SIMPLE_VARIABLE("inactivity-lock-timeout", set_inactivity_lock_timeout, tr("unsigned integer (seconds, 0 to disable)")); + CHECK_SIMPLE_VARIABLE("setup-background-mining", set_setup_background_mining, tr("1/yes or 0/no")); +@@ -4915,7 +5028,10 @@ std::string simple_wallet::get_mnemonic_language() + //---------------------------------------------------------------------------------------------------- + boost::optional<tools::password_container> simple_wallet::get_and_verify_password() const + { +- auto pwd_container = default_password_prompter(m_wallet_file.empty()); ++ const bool verify = m_wallet_file.empty(); ++ auto pwd_container = (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword) ++ ? background_sync_cache_password_prompter(verify) ++ : default_password_prompter(verify); + if (!pwd_container) + return boost::none; + +@@ -5218,6 +5334,8 @@ boost::optional<epee::wipeable_string> simple_wallet::open_wallet(const boost::p + prefix = tr("Opened watch-only wallet"); + else if (m_wallet->multisig(&ready, &threshold, &total)) + prefix = (boost::format(tr("Opened %u/%u multisig wallet%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); ++ else if (m_wallet->is_background_wallet()) ++ prefix = tr("Opened background wallet"); + else + prefix = tr("Opened wallet"); + message_writer(console_color_white, true) << +@@ -5426,6 +5544,10 @@ void simple_wallet::stop_background_mining() + //---------------------------------------------------------------------------------------------------- + void simple_wallet::check_background_mining(const epee::wipeable_string &password) + { ++ // Background mining can be toggled from the main wallet ++ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) ++ return; ++ + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); + if (setup == tools::wallet2::BackgroundMiningNo) + { +@@ -6290,6 +6412,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::rescan_spent(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot rescan spent"); + if (!m_wallet->is_trusted_daemon()) + { + fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); +@@ -6547,10 +6670,27 @@ void simple_wallet::check_for_inactivity_lock(bool user) + " || ||" << std::endl << + "" << std::endl; + } ++ ++ bool started_background_sync = false; ++ if (!m_wallet->is_background_wallet() && ++ m_wallet->background_sync_type() != tools::wallet2::BackgroundSyncOff) ++ { ++ LOCK_IDLE_SCOPE(); ++ m_wallet->start_background_sync(); ++ started_background_sync = true; ++ } ++ + while (1) + { + const char *inactivity_msg = user ? "" : tr("Locked due to inactivity."); +- tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << tr("The wallet password is required to unlock the console."); ++ tools::msg_writer() << inactivity_msg << (inactivity_msg[0] ? " " : "") << ( ++ (m_wallet->is_background_wallet() && m_wallet->background_sync_type() == tools::wallet2::BackgroundSyncCustomPassword) ++ ? tr("The background password is required to unlock the console.") ++ : tr("The wallet password is required to unlock the console.") ++ ); ++ ++ if (m_wallet->is_background_syncing()) ++ tools::msg_writer() << tr("\nSyncing in the background while locked...") << std::endl; + + const bool show_wallet_name = m_wallet->show_wallet_name_when_locked(); + if (show_wallet_name) +@@ -6563,8 +6703,16 @@ void simple_wallet::check_for_inactivity_lock(bool user) + } + try + { +- if (get_and_verify_password()) ++ const auto pwd_container = get_and_verify_password(); ++ if (pwd_container) ++ { ++ if (started_background_sync) ++ { ++ LOCK_IDLE_SCOPE(); ++ m_wallet->stop_background_sync(pwd_container->password()); ++ } + break; ++ } + } + catch (...) { /* do nothing, just let the loop loop */ } + } +@@ -6591,6 +6739,7 @@ bool simple_wallet::on_command(bool (simple_wallet::*cmd)(const std::vector<std: + bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::string> &args_, bool called_by_mms) + { + // "transfer [index=<N1>[,<N2>,...]] [<priority>] [<ring_size>] <address> <amount> [<payment_id>]" ++ CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); + if (!try_connect_to_daemon()) + return false; + +@@ -7064,6 +7213,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::transfer(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_TRANSFER); +@@ -7075,6 +7225,7 @@ bool simple_wallet::transfer(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot transfer"); + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_TRANSFER); +@@ -7086,6 +7237,7 @@ bool simple_wallet::locked_transfer(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + if (args_.size() < 1) + { + PRINT_USAGE(USAGE_LOCKED_SWEEP_ALL); +@@ -7098,6 +7250,7 @@ bool simple_wallet::locked_sweep_all(const std::vector<std::string> &args_) + + bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + if (!try_connect_to_daemon()) + return true; + +@@ -7205,6 +7358,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + auto print_usage = [this, account, below]() + { + if (below) +@@ -7521,6 +7675,7 @@ bool simple_wallet::sweep_main(uint32_t account, uint64_t below, bool locked, co + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::sweep_single(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + if (!try_connect_to_daemon()) + return true; + +@@ -7759,12 +7914,14 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::sweep_all(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + sweep_main(m_current_subaddress_account, 0, false, args_); + return true; + } + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::sweep_account(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + auto local_args = args_; + if (local_args.empty()) + { +@@ -7785,6 +7942,7 @@ bool simple_wallet::sweep_account(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::sweep_below(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sweep"); + uint64_t below = 0; + if (args_.size() < 1) + { +@@ -7803,6 +7961,7 @@ bool simple_wallet::sweep_below(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::donate(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot donate"); + std::vector<std::string> local_args = args_; + if(local_args.empty() || local_args.size() > 5) + { +@@ -7864,6 +8023,7 @@ bool simple_wallet::donate(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::accept_loaded_tx(const std::function<size_t()> get_num_txes, const std::function<const tools::wallet2::tx_construction_data&(size_t)> &get_tx, const std::string &extra_message) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot load tx"); + // gather info to ask the user + uint64_t amount = 0, amount_to_dests = 0, change = 0; + size_t min_ring_size = ~0; +@@ -8044,6 +8204,7 @@ bool simple_wallet::sign_transfer(const std::vector<std::string> &args_) + fail_msg_writer() << tr("This is a watch only wallet"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("cannot sign transfer"); + + bool export_raw = false; + std::string unsigned_filename = "unsigned_monero_tx"; +@@ -8151,6 +8312,8 @@ std::string get_tx_key_stream(crypto::secret_key tx_key, std::vector<crypto::sec + + bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get tx key"); ++ + std::vector<std::string> local_args = args_; + + if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR) +@@ -8191,6 +8354,8 @@ bool simple_wallet::get_tx_key(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot set tx key"); ++ + std::vector<std::string> local_args = args_; + + if(local_args.size() != 2 && local_args.size() != 3) { +@@ -8267,6 +8432,8 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::get_tx_proof(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get tx proof"); ++ + if (args.size() != 2 && args.size() != 3) + { + PRINT_USAGE(USAGE_GET_TX_PROOF); +@@ -8473,6 +8640,7 @@ bool simple_wallet::check_tx_proof(const std::vector<std::string> &args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::get_spend_proof(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get spend proof"); + if (m_wallet->key_on_device()) + { + fail_msg_writer() << tr("command not supported by HW wallet"); +@@ -8557,6 +8725,7 @@ bool simple_wallet::check_spend_proof(const std::vector<std::string> &args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::get_reserve_proof(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get reserve proof"); + if (m_wallet->key_on_device()) + { + fail_msg_writer() << tr("command not supported by HW wallet"); +@@ -9243,6 +9412,8 @@ bool simple_wallet::unspent_outputs(const std::vector<std::string> &args_) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::rescan_blockchain(const std::vector<std::string> &args_) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot rescan"); ++ + uint64_t start_height = 0; + ResetType reset_type = ResetSoft; + +@@ -9540,6 +9711,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector + if (command == "new") + { + // create a new account and switch to it ++ CHECK_IF_BACKGROUND_SYNCING("cannot create new account"); + std::string label = boost::join(local_args, " "); + if (label.empty()) + label = tr("(Untitled account)"); +@@ -9570,6 +9742,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector + else if (command == "label" && local_args.size() >= 1) + { + // set label of the specified account ++ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); + uint32_t index_major; + if (!epee::string_tools::get_xtype_from_string(index_major, local_args[0])) + { +@@ -9591,6 +9764,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector + } + else if (command == "tag" && local_args.size() >= 2) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); + const std::string tag = local_args[0]; + std::set<uint32_t> account_indices; + for (size_t i = 1; i < local_args.size(); ++i) +@@ -9615,6 +9789,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector + } + else if (command == "untag" && local_args.size() >= 1) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); + std::set<uint32_t> account_indices; + for (size_t i = 0; i < local_args.size(); ++i) + { +@@ -9638,6 +9813,7 @@ bool simple_wallet::account(const std::vector<std::string> &args/* = std::vector + } + else if (command == "tag_description" && local_args.size() >= 1) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot modify account"); + const std::string tag = local_args[0]; + std::string description; + if (local_args.size() > 1) +@@ -9755,6 +9931,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: + } + else if (local_args[0] == "new") + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot add address"); + local_args.erase(local_args.begin()); + std::string label; + if (local_args.size() > 0) +@@ -9767,6 +9944,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: + } + else if (local_args[0] == "mnew") + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot add addresses"); + local_args.erase(local_args.begin()); + if (local_args.size() != 1) + { +@@ -9792,6 +9970,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: + } + else if (local_args[0] == "one-off") + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot add address"); + local_args.erase(local_args.begin()); + std::string label; + if (local_args.size() != 2) +@@ -9810,6 +9989,7 @@ bool simple_wallet::print_address(const std::vector<std::string> &args/* = std:: + } + else if (local_args.size() >= 2 && local_args[0] == "label") + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot modify address"); + if (!epee::string_tools::get_xtype_from_string(index, local_args[1])) + { + fail_msg_writer() << tr("failed to parse index: ") << local_args[1]; +@@ -9956,6 +10136,8 @@ bool simple_wallet::print_integrated_address(const std::vector<std::string> &arg + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::vector<std::string>()*/) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get address book"); ++ + if (args.size() == 0) + { + } +@@ -10016,6 +10198,8 @@ bool simple_wallet::address_book(const std::vector<std::string> &args/* = std::v + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::set_tx_note(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot set tx note"); ++ + if (args.size() == 0) + { + PRINT_USAGE(USAGE_SET_TX_NOTE); +@@ -10044,6 +10228,8 @@ bool simple_wallet::set_tx_note(const std::vector<std::string> &args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::get_tx_note(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get tx note"); ++ + if (args.size() != 1) + { + PRINT_USAGE(USAGE_GET_TX_NOTE); +@@ -10069,6 +10255,8 @@ bool simple_wallet::get_tx_note(const std::vector<std::string> &args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::set_description(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot set description"); ++ + // 0 arguments allowed, for setting the description to empty string + + std::string description = ""; +@@ -10085,6 +10273,8 @@ bool simple_wallet::set_description(const std::vector<std::string> &args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::get_description(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot get description"); ++ + if (args.size() != 0) + { + PRINT_USAGE(USAGE_GET_DESCRIPTION); +@@ -10143,6 +10333,8 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args) + type = tr("Watch only"); + else if (m_wallet->multisig(&ready, &threshold, &total)) + type = (boost::format(tr("%u/%u multisig%s")) % threshold % total % (ready ? "" : " (not yet finalized)")).str(); ++ else if (m_wallet->is_background_wallet()) ++ type = tr("Background wallet"); + else + type = tr("Normal"); + message_writer() << tr("Type: ") << type; +@@ -10154,6 +10346,7 @@ bool simple_wallet::wallet_info(const std::vector<std::string> &args) + //---------------------------------------------------------------------------------------------------- + bool simple_wallet::sign(const std::vector<std::string> &args) + { ++ CHECK_IF_BACKGROUND_SYNCING("cannot sign"); + if (m_wallet->key_on_device()) + { + fail_msg_writer() << tr("command not supported by HW wallet"); +@@ -10261,6 +10454,7 @@ bool simple_wallet::export_key_images(const std::vector<std::string> &args_) + fail_msg_writer() << tr("command not supported by HW wallet"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("cannot export key images"); + auto args = args_; + + if (m_wallet->watch_only()) +@@ -10314,6 +10508,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) + fail_msg_writer() << tr("command not supported by HW wallet"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("cannot import key images"); + if (!m_wallet->is_trusted_daemon()) + { + fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); +@@ -10422,6 +10617,7 @@ bool simple_wallet::export_outputs(const std::vector<std::string> &args_) + fail_msg_writer() << tr("command not supported by HW wallet"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("cannot export outputs"); + auto args = args_; + + bool all = false; +@@ -10471,6 +10667,7 @@ bool simple_wallet::import_outputs(const std::vector<std::string> &args) + fail_msg_writer() << tr("command not supported by HW wallet"); + return true; + } ++ CHECK_IF_BACKGROUND_SYNCING("cannot import outputs"); + if (args.size() != 1) + { + PRINT_USAGE(USAGE_IMPORT_OUTPUTS); +diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h +index 7c45d45e8..79c739cdd 100644 +--- a/src/simplewallet/simplewallet.h ++++ b/src/simplewallet/simplewallet.h +@@ -147,6 +147,7 @@ namespace cryptonote + bool set_ignore_outputs_above(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_ignore_outputs_below(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_track_uses(const std::vector<std::string> &args = std::vector<std::string>()); ++ bool setup_background_sync(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_show_wallet_name_when_locked(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_inactivity_lock_timeout(const std::vector<std::string> &args = std::vector<std::string>()); + bool set_setup_background_mining(const std::vector<std::string> &args = std::vector<std::string>()); +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 472f05016..1a9c6f674 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -54,6 +54,40 @@ using namespace cryptonote; + #undef MONERO_DEFAULT_LOG_CATEGORY + #define MONERO_DEFAULT_LOG_CATEGORY "WalletAPI" + ++#define LOCK_REFRESH() \ ++ bool refresh_enabled = m_refreshEnabled; \ ++ m_refreshEnabled = false; \ ++ m_wallet->stop(); \ ++ m_refreshCV.notify_one(); \ ++ boost::mutex::scoped_lock lock(m_refreshMutex); \ ++ boost::mutex::scoped_lock lock2(m_refreshMutex2); \ ++ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ ++ /* m_refreshMutex's still locked here */ \ ++ if (refresh_enabled) \ ++ startRefresh(); \ ++ }) ++ ++#define PRE_VALIDATE_BACKGROUND_SYNC() \ ++ do \ ++ { \ ++ clearStatus(); \ ++ if (m_wallet->key_on_device()) \ ++ { \ ++ setStatusError(tr("HW wallet cannot use background sync")); \ ++ return false; \ ++ } \ ++ if (m_wallet->watch_only()) \ ++ { \ ++ setStatusError(tr("View only wallet cannot use background sync")); \ ++ return false; \ ++ } \ ++ if (m_wallet->multisig()) \ ++ { \ ++ setStatusError(tr("Multisig wallet cannot use background sync")); \ ++ return false; \ ++ } \ ++ } while (0) ++ + namespace Monero { + + namespace { +@@ -814,6 +848,8 @@ bool WalletImpl::close(bool store) + + std::string WalletImpl::seed(const std::string& seed_offset) const + { ++ if (checkBackgroundSync("cannot get seed")) ++ return std::string(); + epee::wipeable_string seed; + if (m_wallet) + m_wallet->get_seed(seed, seed_offset); +@@ -876,6 +912,8 @@ std::string WalletImpl::getSeedLanguage() const + + void WalletImpl::setSeedLanguage(const std::string &arg) + { ++ if (checkBackgroundSync("cannot set seed language")) ++ return; + m_wallet->set_seed_language(arg); + } + +@@ -899,6 +937,8 @@ void WalletImpl::statusWithErrorString(int& status, std::string& errorString) co + + bool WalletImpl::setPassword(const std::string &password) + { ++ if (checkBackgroundSync("cannot change password")) ++ return false; + clearStatus(); + try { + m_wallet->change_password(m_wallet->get_wallet_file(), m_password, password); +@@ -1059,6 +1099,8 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ + + void WalletImpl::setRefreshFromBlockHeight(uint64_t refresh_from_block_height) + { ++ if (checkBackgroundSync("cannot change refresh height")) ++ return; + m_wallet->set_refresh_from_block_height(refresh_from_block_height); + } + +@@ -1176,6 +1218,8 @@ void WalletImpl::refreshAsync() + + bool WalletImpl::rescanBlockchain() + { ++ if (checkBackgroundSync("cannot rescan blockchain")) ++ return false; + clearStatus(); + m_refreshShouldRescan = true; + doRefresh(); +@@ -1184,6 +1228,8 @@ bool WalletImpl::rescanBlockchain() + + void WalletImpl::rescanBlockchainAsync() + { ++ if (checkBackgroundSync("cannot rescan blockchain")) ++ return; + m_refreshShouldRescan = true; + refreshAsync(); + } +@@ -1207,7 +1253,7 @@ int WalletImpl::autoRefreshInterval() const + UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_filename) { + clearStatus(); + UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); +- if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ ++ if (checkBackgroundSync("cannot load tx") || !m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ + setStatusError(tr("Failed to load unsigned transactions")); + transaction->m_status = UnsignedTransaction::Status::Status_Error; + transaction->m_errorString = errorString(); +@@ -1227,6 +1273,8 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file + + bool WalletImpl::submitTransaction(const string &fileName) { + clearStatus(); ++ if (checkBackgroundSync("cannot submit tx")) ++ return false; + std::unique_ptr<PendingTransactionImpl> transaction(new PendingTransactionImpl(*this)); + + bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); +@@ -1250,6 +1298,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) + setStatusError(tr("Wallet is view only")); + return false; + } ++ if (checkBackgroundSync("cannot export key images")) ++ return false; + + try + { +@@ -1270,6 +1320,8 @@ bool WalletImpl::exportKeyImages(const string &filename, bool all) + + bool WalletImpl::importKeyImages(const string &filename) + { ++ if (checkBackgroundSync("cannot import key images")) ++ return false; + if (!trustedDaemon()) { + setStatusError(tr("Key images can only be imported with a trusted daemon")); + return false; +@@ -1293,6 +1345,8 @@ bool WalletImpl::importKeyImages(const string &filename) + + bool WalletImpl::exportOutputs(const string &filename, bool all) + { ++ if (checkBackgroundSync("cannot export outputs")) ++ return false; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets.")) + filename); +@@ -1323,6 +1377,8 @@ bool WalletImpl::exportOutputs(const string &filename, bool all) + + bool WalletImpl::importOutputs(const string &filename) + { ++ if (checkBackgroundSync("cannot import outputs")) ++ return false; + if (m_wallet->key_on_device()) + { + setStatusError(string(tr("Not supported on HW wallets.")) + filename); +@@ -1355,6 +1411,8 @@ bool WalletImpl::importOutputs(const string &filename) + + bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) + { ++ if (checkBackgroundSync("cannot scan transactions")) ++ return false; + if (txids.empty()) + { + setStatusError(string(tr("Failed to scan transactions: no transaction ids provided."))); +@@ -1393,8 +1451,86 @@ bool WalletImpl::scanTransactions(const std::vector<std::string> &txids) + return true; + } + ++bool WalletImpl::setupBackgroundSync(const Wallet::BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) ++{ ++ try ++ { ++ PRE_VALIDATE_BACKGROUND_SYNC(); ++ ++ tools::wallet2::BackgroundSyncType bgs_type; ++ switch (background_sync_type) ++ { ++ case Wallet::BackgroundSync_Off: bgs_type = tools::wallet2::BackgroundSyncOff; break; ++ case Wallet::BackgroundSync_ReusePassword: bgs_type = tools::wallet2::BackgroundSyncReusePassword; break; ++ case Wallet::BackgroundSync_CustomPassword: bgs_type = tools::wallet2::BackgroundSyncCustomPassword; break; ++ default: setStatusError(tr("Unknown background sync type")); return false; ++ } ++ ++ boost::optional<epee::wipeable_string> bgc_password = background_cache_password ++ ? boost::optional<epee::wipeable_string>(*background_cache_password) ++ : boost::none; ++ ++ LOCK_REFRESH(); ++ m_wallet->setup_background_sync(bgs_type, wallet_password, bgc_password); ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Failed to setup background sync: " << e.what()); ++ setStatusError(string(tr("Failed to setup background sync: ")) + e.what()); ++ return false; ++ } ++ return true; ++} ++ ++Wallet::BackgroundSyncType WalletImpl::getBackgroundSyncType() const ++{ ++ switch (m_wallet->background_sync_type()) ++ { ++ case tools::wallet2::BackgroundSyncOff: return Wallet::BackgroundSync_Off; ++ case tools::wallet2::BackgroundSyncReusePassword: return Wallet::BackgroundSync_ReusePassword; ++ case tools::wallet2::BackgroundSyncCustomPassword: return Wallet::BackgroundSync_CustomPassword; ++ default: setStatusError(tr("Unknown background sync type")); return Wallet::BackgroundSync_Off; ++ } ++} ++ ++bool WalletImpl::startBackgroundSync() ++{ ++ try ++ { ++ PRE_VALIDATE_BACKGROUND_SYNC(); ++ LOCK_REFRESH(); ++ m_wallet->start_background_sync(); ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Failed to start background sync: " << e.what()); ++ setStatusError(string(tr("Failed to start background sync: ")) + e.what()); ++ return false; ++ } ++ return true; ++} ++ ++bool WalletImpl::stopBackgroundSync(const std::string &wallet_password) ++{ ++ try ++ { ++ PRE_VALIDATE_BACKGROUND_SYNC(); ++ LOCK_REFRESH(); ++ m_wallet->stop_background_sync(epee::wipeable_string(wallet_password)); ++ } ++ catch (const std::exception &e) ++ { ++ LOG_ERROR("Failed to stop background sync: " << e.what()); ++ setStatusError(string(tr("Failed to stop background sync: ")) + e.what()); ++ return false; ++ } ++ return true; ++} ++ + void WalletImpl::addSubaddressAccount(const std::string& label) + { ++ if (checkBackgroundSync("cannot add account")) ++ return; + m_wallet->add_subaddress_account(label); + } + size_t WalletImpl::numSubaddressAccounts() const +@@ -1407,10 +1543,14 @@ size_t WalletImpl::numSubaddresses(uint32_t accountIndex) const + } + void WalletImpl::addSubaddress(uint32_t accountIndex, const std::string& label) + { ++ if (checkBackgroundSync("cannot add subbaddress")) ++ return; + m_wallet->add_subaddress(accountIndex, label); + } + std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const + { ++ if (checkBackgroundSync("cannot get subbaddress label")) ++ return ""; + try + { + return m_wallet->get_subaddress_label({accountIndex, addressIndex}); +@@ -1424,6 +1564,8 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre + } + void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) + { ++ if (checkBackgroundSync("cannot set subbaddress label")) ++ return; + try + { + return m_wallet->set_subaddress_label({accountIndex, addressIndex}, label); +@@ -1437,12 +1579,16 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex + + MultisigState WalletImpl::multisig() const { + MultisigState state; ++ if (checkBackgroundSync("cannot use multisig")) ++ return state; + state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total); + + return state; + } + + string WalletImpl::getMultisigInfo() const { ++ if (checkBackgroundSync("cannot use multisig")) ++ return string(); + try { + clearStatus(); + return m_wallet->get_multisig_first_kex_msg(); +@@ -1455,6 +1601,8 @@ string WalletImpl::getMultisigInfo() const { + } + + string WalletImpl::makeMultisig(const vector<string>& info, const uint32_t threshold) { ++ if (checkBackgroundSync("cannot make multisig")) ++ return string(); + try { + clearStatus(); + +@@ -1595,6 +1743,9 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + + do { ++ if (checkBackgroundSync("cannot create transactions")) ++ break; ++ + std::vector<uint8_t> extra; + std::string extra_nonce; + vector<cryptonote::tx_destination_entry> dsts; +@@ -1761,6 +1912,9 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() + PendingTransactionImpl * transaction = new PendingTransactionImpl(*this); + + do { ++ if (checkBackgroundSync("cannot sweep")) ++ break; ++ + try { + transaction->m_pending_tx = m_wallet->create_unmixable_sweep_transactions(); + pendingTxPostProcess(transaction); +@@ -1894,11 +2048,15 @@ uint32_t WalletImpl::defaultMixin() const + + void WalletImpl::setDefaultMixin(uint32_t arg) + { ++ if (checkBackgroundSync("cannot set default mixin")) ++ return; + m_wallet->default_mixin(arg); + } + + bool WalletImpl::setCacheAttribute(const std::string &key, const std::string &val) + { ++ if (checkBackgroundSync("cannot set cache attribute")) ++ return false; + m_wallet->set_attribute(key, val); + return true; + } +@@ -1912,6 +2070,8 @@ std::string WalletImpl::getCacheAttribute(const std::string &key) const + + bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) + { ++ if (checkBackgroundSync("cannot set user note")) ++ return false; + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + return false; +@@ -1923,6 +2083,8 @@ bool WalletImpl::setUserNote(const std::string &txid, const std::string ¬e) + + std::string WalletImpl::getUserNote(const std::string &txid) const + { ++ if (checkBackgroundSync("cannot get user note")) ++ return ""; + cryptonote::blobdata txid_data; + if(!epee::string_tools::parse_hexstr_to_binbuff(txid, txid_data) || txid_data.size() != sizeof(crypto::hash)) + return ""; +@@ -1933,6 +2095,9 @@ std::string WalletImpl::getUserNote(const std::string &txid) const + + std::string WalletImpl::getTxKey(const std::string &txid_str) const + { ++ if (checkBackgroundSync("cannot get tx key")) ++ return ""; ++ + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { +@@ -2017,6 +2182,9 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, + + std::string WalletImpl::getTxProof(const std::string &txid_str, const std::string &address_str, const std::string &message) const + { ++ if (checkBackgroundSync("cannot get tx proof")) ++ return ""; ++ + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(txid_str, txid)) + { +@@ -2073,6 +2241,9 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad + } + + std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::string &message) const { ++ if (checkBackgroundSync("cannot get spend proof")) ++ return ""; ++ + crypto::hash txid; + if(!epee::string_tools::hex_to_pod(txid_str, txid)) + { +@@ -2115,6 +2286,9 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string + } + + std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { ++ if (checkBackgroundSync("cannot get reserve proof")) ++ return ""; ++ + try + { + clearStatus(); +@@ -2161,6 +2335,9 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string + + std::string WalletImpl::signMessage(const std::string &message, const std::string &address) + { ++ if (checkBackgroundSync("cannot sign message")) ++ return ""; ++ + if (address.empty()) { + return m_wallet->sign(message, tools::wallet2::sign_with_spend_key); + } +@@ -2288,6 +2465,16 @@ bool WalletImpl::isDeterministic() const + return m_wallet->is_deterministic(); + } + ++bool WalletImpl::isBackgroundSyncing() const ++{ ++ return m_wallet->is_background_syncing(); ++} ++ ++bool WalletImpl::isBackgroundWallet() const ++{ ++ return m_wallet->is_background_wallet(); ++} ++ + void WalletImpl::clearStatus() const + { + boost::lock_guard<boost::mutex> l(m_statusMutex); +@@ -2356,9 +2543,7 @@ void WalletImpl::doRefresh() + if(rescan) + m_wallet->rescan_blockchain(false); + m_wallet->refresh(trustedDaemon()); +- if (!m_synchronized) { +- m_synchronized = true; +- } ++ m_synchronized = m_wallet->is_synced(); + // assuming if we have empty history, it wasn't initialized yet + // for further history changes client need to update history in + // "on_money_received" and "on_money_sent" callbacks +@@ -2462,6 +2647,24 @@ bool WalletImpl::doInit(const string &daemon_address, const std::string &proxy_a + return true; + } + ++bool WalletImpl::checkBackgroundSync(const std::string &message) const ++{ ++ clearStatus(); ++ if (m_wallet->is_background_wallet()) ++ { ++ LOG_ERROR("Background wallets " + message); ++ setStatusError(tr("Background wallets ") + message); ++ return true; ++ } ++ if (m_wallet->is_background_syncing()) ++ { ++ LOG_ERROR(message + " while background syncing"); ++ setStatusError(message + tr(" while background syncing. Stop background syncing first.")); ++ return true; ++ } ++ return false; ++} ++ + bool WalletImpl::parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &amount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) + { + return m_wallet->parse_uri(uri, address, payment_id, amount, tx_description, recipient_name, unknown_parameters, error); +@@ -2480,6 +2683,8 @@ std::string WalletImpl::getDefaultDataDir() const + bool WalletImpl::rescanSpent() + { + clearStatus(); ++ if (checkBackgroundSync("cannot rescan spent")) ++ return false; + if (!trustedDaemon()) { + setStatusError(tr("Rescan spent can only be used with a trusted daemon")); + return false; +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 787215ab3..9acd2871f 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -181,6 +181,13 @@ public: + bool importOutputs(const std::string &filename) override; + bool scanTransactions(const std::vector<std::string> &txids) override; + ++ bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password = optional<std::string>()) override; ++ BackgroundSyncType getBackgroundSyncType() const override; ++ bool startBackgroundSync() override; ++ bool stopBackgroundSync(const std::string &wallet_password) override; ++ bool isBackgroundSyncing() const override; ++ bool isBackgroundWallet() const override; ++ + virtual void disposeTransaction(PendingTransaction * t) override; + virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const override; +@@ -249,6 +256,7 @@ private: + bool isNewWallet() const; + void pendingTxPostProcess(PendingTransactionImpl * pending); + bool doInit(const std::string &daemon_address, const std::string &proxy_address, uint64_t upper_transaction_size_limit = 0, bool ssl = false); ++ bool checkBackgroundSync(const std::string &message) const; + + private: + friend class PendingTransactionImpl; +@@ -263,6 +271,10 @@ private: + mutable boost::mutex m_statusMutex; + mutable int m_status; + mutable std::string m_errorString; ++ // TODO: harden password handling in the wallet API, see relevant discussion ++ // https://github.com/monero-project/monero-gui/issues/1537 ++ // https://github.com/feather-wallet/feather/issues/72#issuecomment-1405602142 ++ // https://github.com/monero-project/monero/pull/8619#issuecomment-1632951461 + std::string m_password; + std::unique_ptr<TransactionHistoryImpl> m_history; + std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback; +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 9ea753083..4268b656e 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -445,6 +445,12 @@ struct Wallet + ConnectionStatus_WrongVersion + }; + ++ enum BackgroundSyncType { ++ BackgroundSync_Off = 0, ++ BackgroundSync_ReusePassword = 1, ++ BackgroundSync_CustomPassword = 2 ++ }; ++ + virtual ~Wallet() = 0; + virtual std::string seed(const std::string& seed_offset = "") const = 0; + virtual std::string getSeedLanguage() const = 0; +@@ -940,6 +946,42 @@ struct Wallet + */ + virtual bool scanTransactions(const std::vector<std::string> &txids) = 0; + ++ /*! ++ * \brief setupBackgroundSync - setup background sync mode with just a view key ++ * \param background_sync_type - the mode the wallet background syncs in ++ * \param wallet_password ++ * \param background_cache_password - custom password to encrypt background cache, only needed for custom password background sync type ++ * \return - true on success ++ */ ++ virtual bool setupBackgroundSync(const BackgroundSyncType background_sync_type, const std::string &wallet_password, const optional<std::string> &background_cache_password) = 0; ++ ++ /*! ++ * \brief getBackgroundSyncType - get mode the wallet background syncs in ++ * \return - the type, or off if type is unknown ++ */ ++ virtual BackgroundSyncType getBackgroundSyncType() const = 0; ++ ++ /** ++ * @brief startBackgroundSync - sync the chain in the background with just view key ++ */ ++ virtual bool startBackgroundSync() = 0; ++ ++ /** ++ * @brief stopBackgroundSync - bring back spend key and process background synced txs ++ * \param wallet_password ++ */ ++ virtual bool stopBackgroundSync(const std::string &wallet_password) = 0; ++ ++ /** ++ * @brief isBackgroundSyncing - returns true if the wallet is background syncing ++ */ ++ virtual bool isBackgroundSyncing() const = 0; ++ ++ /** ++ * @brief isBackgroundWallet - returns true if the wallet is a background wallet ++ */ ++ virtual bool isBackgroundWallet() const = 0; ++ + virtual TransactionHistory * history() = 0; + virtual AddressBook * addressBook() = 0; + virtual Subaddress * subaddress() = 0; +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index e4e02c782..618c43cee 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -1010,14 +1010,14 @@ uint64_t num_priv_multisig_keys_post_setup(uint64_t threshold, uint64_t total) + * @param keys_data_key the chacha key that encrypts wallet keys files + * @return crypto::chacha_key the chacha key that encrypts the wallet cache files + */ +-crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key) ++crypto::chacha_key derive_cache_key(const crypto::chacha_key& keys_data_key, const unsigned char domain_separator) + { + static_assert(HASH_SIZE == sizeof(crypto::chacha_key), "Mismatched sizes of hash and chacha key"); + + crypto::chacha_key cache_key; + epee::mlocked<tools::scrubbed_arr<char, HASH_SIZE+1>> cache_key_data; + memcpy(cache_key_data.data(), &keys_data_key, HASH_SIZE); +- cache_key_data[HASH_SIZE] = config::HASH_KEY_WALLET_CACHE; ++ cache_key_data[HASH_SIZE] = domain_separator; + cn_fast_hash(cache_key_data.data(), HASH_SIZE+1, (crypto::hash&) cache_key); + + return cache_key; +@@ -1105,7 +1105,7 @@ wallet_keys_unlocker::wallet_keys_unlocker(wallet2 &w, const boost::optional<too + boost::lock_guard<boost::mutex> lock(lockers_lock); + if (lockers++ > 0) + locked = false; +- if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only()) ++ if (!locked || w.is_unattended() || w.ask_password() != tools::wallet2::AskPasswordToDecrypt || w.watch_only() || w.is_background_syncing()) + { + locked = false; + return; +@@ -1222,6 +1222,11 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended, std + m_ignore_outputs_above(MONEY_SUPPLY), + m_ignore_outputs_below(0), + m_track_uses(false), ++ m_is_background_wallet(false), ++ m_background_sync_type(BackgroundSyncOff), ++ m_background_syncing(false), ++ m_processing_background_cache(false), ++ m_custom_background_key(boost::none), + m_show_wallet_name_when_locked(false), + m_inactivity_lock_timeout(DEFAULT_INACTIVITY_LOCK_TIMEOUT), + m_setup_background_mining(BackgroundMiningMaybe), +@@ -1877,6 +1882,9 @@ bool has_nonrequested_tx_at_height_or_above_requested(uint64_t height, const std + //---------------------------------------------------------------------------------------------------- + void wallet2::scan_tx(const std::unordered_set<crypto::hash> &txids) + { ++ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, ++ "cannot scan tx from background wallet"); ++ + // Get the transactions from daemon in batches sorted lowest height to highest + tx_entry_data txs_to_scan = get_tx_entries(txids); + if (txs_to_scan.tx_entries.empty()) +@@ -2184,11 +2192,11 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons + THROW_WALLET_EXCEPTION_IF(i >= tx.vout.size(), error::wallet_internal_error, "Invalid vout index"); + + // if keys are encrypted, ask for password +- if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k) ++ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_multisig_rescan_k && !m_background_syncing) + { + static critical_section password_lock; + CRITICAL_REGION_LOCAL(password_lock); +- if (!m_encrypt_keys_after_refresh) ++ if (!m_encrypt_keys_after_refresh && !m_processing_background_cache) + { + boost::optional<epee::wipeable_string> pwd = m_callback->on_get_password(pool ? "output found in pool" : "output received"); + THROW_WALLET_EXCEPTION_IF(!pwd, error::password_needed, tr("Password is needed to compute key image for incoming monero")); +@@ -2200,7 +2208,7 @@ void wallet2::scan_output(const cryptonote::transaction &tx, bool miner_tx, cons + crypto::public_key output_public_key; + THROW_WALLET_EXCEPTION_IF(!get_output_public_key(tx.vout[i], output_public_key), error::wallet_internal_error, "Failed to get output public key"); + +- if (m_multisig) ++ if (m_multisig || m_background_syncing/*no spend key*/) + { + tx_scan_info.in_ephemeral.pub = output_public_key; + tx_scan_info.in_ephemeral.sec = crypto::null_skey; +@@ -2457,6 +2465,14 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + THROW_WALLET_EXCEPTION_IF(tx.vout.size() != o_indices.size(), error::wallet_internal_error, + "transactions outputs size=" + std::to_string(tx.vout.size()) + + " not match with daemon response size=" + std::to_string(o_indices.size())); ++ ++ // we're going to re-process this receive when background sync is disabled ++ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) ++ { ++ size_t bgs_idx = m_background_sync_data.txs.size(); ++ LOG_PRINT_L2("Adding received tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); ++ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}}); ++ } + } + + for(size_t o: outs) +@@ -2482,7 +2498,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + td.m_tx = (const cryptonote::transaction_prefix&)tx; + td.m_txid = txid; + td.m_key_image = tx_scan_info[o].ki; +- td.m_key_image_known = !m_watch_only && !m_multisig; ++ td.m_key_image_known = !m_watch_only && !m_multisig && !m_background_syncing; + if (!td.m_key_image_known) + { + // we might have cold signed, and have a mapping to key images +@@ -2672,10 +2688,17 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + set_spent(it->second, height); + if (!ignore_callbacks && 0 != m_callback) + m_callback->on_money_spent(height, txid, tx, amount, tx, td.m_subaddr_index); ++ ++ if (m_background_syncing && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) ++ { ++ size_t bgs_idx = m_background_sync_data.txs.size(); ++ LOG_PRINT_L2("Adding spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); ++ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}}); ++ } + } + } + +- if (!pool && m_track_uses) ++ if (!pool && (m_track_uses || (m_background_syncing && it == m_key_images.end()))) + { + PERF_TIMER(track_uses); + const uint64_t amount = in_to_key.amount; +@@ -2689,7 +2712,19 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + { + size_t idx = i->second; + THROW_WALLET_EXCEPTION_IF(idx >= m_transfers.size(), error::wallet_internal_error, "Output tracker cache index out of range"); +- m_transfers[idx].m_uses.push_back(std::make_pair(height, txid)); ++ ++ if (m_track_uses) ++ m_transfers[idx].m_uses.push_back(std::make_pair(height, txid)); ++ ++ // We'll re-process all txs which *might* be spends when we disable ++ // background sync and retrieve the spend key. We don't know if an ++ // output is a spend in this tx if we don't know its key image. ++ if (m_background_syncing && !m_transfers[idx].m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) ++ { ++ size_t bgs_idx = m_background_sync_data.txs.size(); ++ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); ++ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}}); ++ } + } + } + } +@@ -2699,7 +2734,16 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + continue; + for (uint64_t offset: offsets) + if (offset == td.m_global_output_index) +- td.m_uses.push_back(std::make_pair(height, txid)); ++ { ++ if (m_track_uses) ++ td.m_uses.push_back(std::make_pair(height, txid)); ++ if (m_background_syncing && !td.m_key_image_known && m_background_sync_data.txs.find(txid) == m_background_sync_data.txs.end()) ++ { ++ size_t bgs_idx = m_background_sync_data.txs.size(); ++ LOG_PRINT_L2("Adding plausible spent tx " << txid << " to background sync data (idx=" << bgs_idx << ")"); ++ m_background_sync_data.txs.insert({txid, background_synced_tx{bgs_idx, tx, o_indices, height, ts, double_spend_seen}}); ++ } ++ } + } + } + } +@@ -3072,8 +3116,8 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh + req.start_height = start_height; + req.no_miner_tx = m_refresh_type == RefreshNoCoinbase; + +- req.requested_info = first ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; +- if (try_incremental) ++ req.requested_info = (first && !m_background_syncing) ? COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_AND_POOL : COMMAND_RPC_GET_BLOCKS_FAST::BLOCKS_ONLY; ++ if (try_incremental && !m_background_syncing) + req.pool_info_since = m_pool_info_query_time; + + { +@@ -3100,7 +3144,7 @@ void wallet2::pull_blocks(bool first, bool try_incremental, uint64_t start_heigh + << ", height " << blocks_start_height + blocks.size() << ", node height " << res.current_height + << ", pool info " << static_cast<unsigned int>(res.pool_info_extent)); + +- if (first) ++ if (first && !m_background_syncing) + { + if (res.pool_info_extent != COMMAND_RPC_GET_BLOCKS_FAST::NONE) + { +@@ -3612,6 +3656,9 @@ void wallet2::process_unconfirmed_transfer(bool incremental, const crypto::hash + // incremental update anymore, because with that we might miss some txs altogether. + void wallet2::update_pool_state(std::vector<std::tuple<cryptonote::transaction, crypto::hash, bool>> &process_txs, bool refreshed, bool try_incremental) + { ++ process_txs.clear(); ++ if (m_background_syncing) ++ return; + bool updated = false; + if (m_pool_info_query_time != 0 && try_incremental) + { +@@ -4182,6 +4229,8 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo + } + + m_first_refresh_done = true; ++ if (m_background_syncing || m_is_background_wallet) ++ m_background_sync_data.first_refresh_done = true; + + LOG_PRINT_L1("Refresh done, blocks received: " << blocks_fetched << ", balance (all accounts): " << print_money(balance_all(false)) << ", unlocked: " << print_money(unlocked_balance_all(false))); + } +@@ -4267,6 +4316,14 @@ wallet2::detached_blockchain_data wallet2::detach_blockchain(uint64_t height, st + td.m_uses.pop_back(); + } + ++ for (auto it = m_background_sync_data.txs.begin(); it != m_background_sync_data.txs.end(); ) ++ { ++ if(height <= it->second.height) ++ it = m_background_sync_data.txs.erase(it); ++ else ++ ++it; ++ } ++ + if (output_tracker_cache) + output_tracker_cache->clear(); + +@@ -4343,6 +4400,9 @@ void wallet2::handle_reorg(uint64_t height, std::map<std::pair<uint64_t, uint64_ + error::wallet_internal_error, "Daemon claims reorg below last checkpoint"); + detached_blockchain_data dbd = detach_blockchain(height, output_tracker_cache); + ++ if (m_background_syncing && height < m_background_sync_data.start_height) ++ m_background_sync_data.start_height = height; ++ + if (m_callback) + m_callback->on_reorg(height, dbd.detached_blockchain.size(), dbd.detached_tx_hashes.size()); + } +@@ -4352,6 +4412,7 @@ bool wallet2::deinit() + if(m_is_initialized) { + m_is_initialized = false; + unlock_keys_file(); ++ unlock_background_keys_file(); + m_account.deinit(); + } + return true; +@@ -4378,6 +4439,9 @@ bool wallet2::clear() + m_device_last_key_image_sync = 0; + m_pool_info_query_time = 0; + m_skip_to_height = 0; ++ m_background_sync_data.first_refresh_done = false; ++ m_background_sync_data.start_height = 0; ++ m_background_sync_data.txs.clear(); + return true; + } + //---------------------------------------------------------------------------------------------------- +@@ -4396,13 +4460,32 @@ void wallet2::clear_soft(bool keep_key_images) + m_scanned_pool_txs[1].clear(); + m_pool_info_query_time = 0; + m_skip_to_height = 0; ++ m_background_sync_data.first_refresh_done = false; ++ m_background_sync_data.start_height = 0; ++ m_background_sync_data.txs.clear(); + + cryptonote::block b; + generate_genesis(b); + m_blockchain.push_back(get_block_hash(b)); + m_last_block_reward = cryptonote::get_outs_money_amount(b.miner_tx); + } +- ++//---------------------------------------------------------------------------------------------------- ++void wallet2::clear_user_data() ++{ ++ for (auto i = m_confirmed_txs.begin(); i != m_confirmed_txs.end(); ++i) ++ i->second.m_dests.clear(); ++ for (auto i = m_unconfirmed_txs.begin(); i != m_unconfirmed_txs.end(); ++i) ++ i->second.m_dests.clear(); ++ for (auto i = m_transfers.begin(); i != m_transfers.end(); ++i) ++ i->m_frozen = false; ++ m_tx_keys.clear(); ++ m_tx_notes.clear(); ++ m_address_book.clear(); ++ m_subaddress_labels.clear(); ++ m_attributes.clear(); ++ m_account_tags = std::pair<serializable_map<std::string, std::string>, std::vector<std::string>>(); ++} ++//---------------------------------------------------------------------------------------------------- + /*! + * \brief Stores wallet information to wallet file. + * \param keys_file_name Name of wallet file +@@ -4414,16 +4497,35 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable + { + boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(password, watch_only); + CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); +- ++ return store_keys_file_data(keys_file_name, keys_file_data.get()); ++} ++//---------------------------------------------------------------------------------------------------- ++bool wallet2::store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only, bool background_keys_file) ++{ ++ boost::optional<wallet2::keys_file_data> keys_file_data = get_keys_file_data(key, watch_only, background_keys_file); ++ CHECK_AND_ASSERT_MES(keys_file_data != boost::none, false, "failed to generate wallet keys data"); ++ return store_keys_file_data(keys_file_name, keys_file_data.get(), background_keys_file); ++} ++//---------------------------------------------------------------------------------------------------- ++bool wallet2::store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file) ++{ + std::string tmp_file_name = keys_file_name + ".new"; + std::string buf; +- bool r = ::serialization::dump_binary(keys_file_data.get(), buf); ++ bool r = ::serialization::dump_binary(keys_file_data, buf); + r = r && save_to_file(tmp_file_name, buf); + CHECK_AND_ASSERT_MES(r, false, "failed to generate wallet keys file " << tmp_file_name); + +- unlock_keys_file(); ++ if (!background_keys_file) ++ unlock_keys_file(); ++ else ++ unlock_background_keys_file(); ++ + std::error_code e = tools::replace_file(tmp_file_name, keys_file_name); +- lock_keys_file(); ++ ++ if (!background_keys_file) ++ lock_keys_file(); ++ else ++ lock_background_keys_file(keys_file_name); + + if (e) { + boost::filesystem::remove(tmp_file_name); +@@ -4435,26 +4537,27 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable + } + //---------------------------------------------------------------------------------------------------- + boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee::wipeable_string& password, bool watch_only) ++{ ++ crypto::chacha_key key; ++ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); ++ verify_password_with_cached_key(key); ++ return get_keys_file_data(key, watch_only); ++} ++//---------------------------------------------------------------------------------------------------- ++boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const crypto::chacha_key& key, bool watch_only, bool background_keys_file) + { + epee::byte_slice account_data; + std::string multisig_signers; + std::string multisig_derivations; + cryptonote::account_base account = m_account; + +- crypto::chacha_key key; +- crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); +- +- // We use m_cache_key as a deterministic test to see if given key corresponds to original password +- const crypto::chacha_key cache_key = derive_cache_key(key); +- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); +- + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + { + account.encrypt_viewkey(key); + account.decrypt_keys(key); + } + +- if (watch_only) ++ if (watch_only || background_keys_file) + account.forget_spend_key(); + + account.encrypt_keys(key); +@@ -4589,6 +4692,9 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: + value2.SetInt(m_track_uses ? 1 : 0); + json.AddMember("track_uses", value2, json.GetAllocator()); + ++ value2.SetInt(m_background_sync_type); ++ json.AddMember("background_sync_type", value2, json.GetAllocator()); ++ + value2.SetInt(m_show_wallet_name_when_locked ? 1 : 0); + json.AddMember("show_wallet_name_when_locked", value2, json.GetAllocator()); + +@@ -4646,6 +4752,13 @@ boost::optional<wallet2::keys_file_data> wallet2::get_keys_file_data(const epee: + value2.SetInt(m_enable_multisig ? 1 : 0); + json.AddMember("enable_multisig", value2, json.GetAllocator()); + ++ if (m_background_sync_type == BackgroundSyncCustomPassword && !background_keys_file && m_custom_background_key) ++ { ++ value.SetString(reinterpret_cast<const char*>(m_custom_background_key.get().data()), m_custom_background_key.get().size()); ++ json.AddMember("custom_background_key", value, json.GetAllocator()); ++ } ++ ++ + value2.SetInt(m_polyseed ? 1 : 0); + json.AddMember("polyseed", value2, json.GetAllocator()); + +@@ -4675,13 +4788,81 @@ void wallet2::setup_keys(const epee::wipeable_string &password) + m_account.decrypt_viewkey(key); + } + +- m_cache_key = derive_cache_key(key); ++ m_cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE); + + get_ringdb_key(); + } + //---------------------------------------------------------------------------------------------------- ++void validate_background_cache_password_usage(const tools::wallet2::BackgroundSyncType background_sync_type, const boost::optional<epee::wipeable_string> &background_cache_password, const bool multisig, const bool watch_only, const bool key_on_device) ++{ ++ THROW_WALLET_EXCEPTION_IF(multisig || watch_only || key_on_device, error::wallet_internal_error, multisig ++ ? "Background sync not implemented for multisig wallets" : watch_only ++ ? "Background sync not implemented for view only wallets" ++ : "Background sync not implemented for HW wallets"); ++ ++ switch (background_sync_type) ++ { ++ case tools::wallet2::BackgroundSyncOff: ++ { ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "background sync is not enabled"); ++ break; ++ } ++ case tools::wallet2::BackgroundSyncReusePassword: ++ { ++ THROW_WALLET_EXCEPTION_IF(background_cache_password, error::wallet_internal_error, ++ "unexpected custom background cache password"); ++ break; ++ } ++ case tools::wallet2::BackgroundSyncCustomPassword: ++ { ++ THROW_WALLET_EXCEPTION_IF(!background_cache_password, error::wallet_internal_error, ++ "expected custom background cache password"); ++ break; ++ } ++ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); ++ } ++} ++//---------------------------------------------------------------------------------------------------- ++void get_custom_background_key(const epee::wipeable_string &password, crypto::chacha_key &custom_background_key, const uint64_t kdf_rounds) ++{ ++ crypto::chacha_key key; ++ crypto::generate_chacha_key(password.data(), password.size(), key, kdf_rounds); ++ custom_background_key = derive_cache_key(key, config::HASH_KEY_BACKGROUND_KEYS_FILE); ++} ++//---------------------------------------------------------------------------------------------------- ++const crypto::chacha_key wallet2::get_cache_key() ++{ ++ if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing) ++ { ++ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); ++ // Domain separate keys used to encrypt background keys file and cache ++ return derive_cache_key(m_custom_background_key.get(), config::HASH_KEY_BACKGROUND_CACHE); ++ } ++ else ++ { ++ return m_cache_key; ++ } ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::verify_password_with_cached_key(const epee::wipeable_string &password) ++{ ++ crypto::chacha_key key; ++ crypto::generate_chacha_key(password.data(), password.size(), key, m_kdf_rounds); ++ verify_password_with_cached_key(key); ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::verify_password_with_cached_key(const crypto::chacha_key &key) ++{ ++ // We use m_cache_key as a deterministic test to see if given key corresponds to original password ++ const crypto::chacha_key cache_key = derive_cache_key(key, config::HASH_KEY_WALLET_CACHE); ++ THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); ++} ++//---------------------------------------------------------------------------------------------------- + void wallet2::change_password(const std::string &filename, const epee::wipeable_string &original_password, const epee::wipeable_string &new_password) + { ++ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, ++ "cannot change password from background wallet"); ++ + if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) + decrypt_keys(original_password); + setup_keys(new_password); +@@ -4740,8 +4921,24 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + std::string account_data; + account_data.resize(keys_file_data.account_data.size()); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); +- if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) ++ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject(); ++ if (try_v0_format) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); ++ ++ // Check if it's a background keys file if both of the above formats fail ++ { ++ m_is_background_wallet = false; ++ m_background_syncing = false; ++ cryptonote::account_base account_data_check; ++ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data)) ++ { ++ get_custom_background_key(password, key, m_kdf_rounds); ++ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); ++ m_is_background_wallet = !json.Parse(account_data.c_str()).HasParseError() && json.IsObject(); ++ m_background_syncing = m_is_background_wallet; // start a background wallet background syncing ++ } ++ } ++ + // The contents should be JSON if the wallet follows the new format. + if (json.Parse(account_data.c_str()).HasParseError()) + { +@@ -4779,6 +4976,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + m_ignore_outputs_above = MONEY_SUPPLY; + m_ignore_outputs_below = 0; + m_track_uses = false; ++ m_background_sync_type = BackgroundSyncOff; + m_show_wallet_name_when_locked = false; + m_inactivity_lock_timeout = DEFAULT_INACTIVITY_LOCK_TIMEOUT; + m_setup_background_mining = BackgroundMiningMaybe; +@@ -4796,6 +4994,7 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + m_credits_target = 0; + m_enable_multisig = false; + m_allow_mismatched_daemon_version = false; ++ m_custom_background_key = boost::none; + m_polyseed = false; + } + else if(json.IsObject()) +@@ -5034,6 +5233,40 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, enable_multisig, int, Int, false, false); + m_enable_multisig = field_enable_multisig; + GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, polyseed, int, Int, false, false); ++ ++ GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, background_sync_type, BackgroundSyncType, Int, false, BackgroundSyncOff); ++ m_background_sync_type = field_background_sync_type; ++ ++ // Load encryption key used to encrypt background cache ++ crypto::chacha_key custom_background_key; ++ m_custom_background_key = boost::none; ++ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_is_background_wallet) ++ { ++ if (!json.HasMember("custom_background_key")) ++ { ++ LOG_ERROR("Field custom_background_key not found in JSON"); ++ return false; ++ } ++ else if (!json["custom_background_key"].IsString()) ++ { ++ LOG_ERROR("Field custom_background_key found in JSON, but not String"); ++ return false; ++ } ++ else if (json["custom_background_key"].GetStringLength() != sizeof(crypto::chacha_key)) ++ { ++ LOG_ERROR("Field custom_background_key found in JSON, but not correct length"); ++ return false; ++ } ++ const char *field_custom_background_key = json["custom_background_key"].GetString(); ++ memcpy(custom_background_key.data(), field_custom_background_key, sizeof(crypto::chacha_key)); ++ m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key); ++ LOG_PRINT_L1("Loaded custom background key derived from custom password"); ++ } ++ else if (json.HasMember("custom_background_key")) ++ { ++ LOG_ERROR("Unexpected field custom_background_key found in JSON"); ++ } ++ + m_polyseed = field_polyseed; + } + else +@@ -5098,12 +5331,17 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + const cryptonote::account_keys& keys = m_account.get_keys(); + hw::device &hwdev = m_account.get_device(); + r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); +- if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD) ++ if (!m_watch_only && !m_multisig && hwdev.device_protocol() != hw::device::PROTOCOL_COLD && !m_is_background_wallet) + r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_files_doesnt_correspond, m_keys_file, m_wallet_file); + + if (r) +- setup_keys(password); ++ { ++ if (!m_is_background_wallet) ++ setup_keys(password); ++ else ++ m_custom_background_key = boost::optional<crypto::chacha_key>(key); ++ } + + return true; + } +@@ -5118,11 +5356,12 @@ bool wallet2::load_keys_buf(const std::string& keys_buf, const epee::wipeable_st + * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password + * + */ +-bool wallet2::verify_password(const epee::wipeable_string& password) ++bool wallet2::verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out) + { + // this temporary unlocking is necessary for Windows (otherwise the file couldn't be loaded). + unlock_keys_file(); +- bool r = verify_password(m_keys_file, password, m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig, m_account.get_device(), m_kdf_rounds); ++ const bool no_spend_key = m_account.get_device().device_protocol() == hw::device::PROTOCOL_COLD || m_watch_only || m_multisig || m_is_background_wallet; ++ bool r = verify_password(m_keys_file, password, no_spend_key, m_account.get_device(), m_kdf_rounds, spend_key_out); + lock_keys_file(); + return r; + } +@@ -5140,7 +5379,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) + * can be used prior to rewriting wallet keys file, to ensure user has entered the correct password + * + */ +-bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) ++bool wallet2::verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out) + { + rapidjson::Document json; + wallet2::keys_file_data keys_file_data; +@@ -5157,9 +5396,22 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip + std::string account_data; + account_data.resize(keys_file_data.account_data.size()); + crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); +- if (json.Parse(account_data.c_str()).HasParseError() || !json.IsObject()) ++ const bool try_v0_format = json.Parse(account_data.c_str()).HasParseError() || !json.IsObject(); ++ if (try_v0_format) + crypto::chacha8(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); + ++ // Check if it's a background keys file if both of the above formats fail ++ { ++ cryptonote::account_base account_data_check; ++ if (try_v0_format && !epee::serialization::load_t_from_binary(account_data_check, account_data)) ++ { ++ get_custom_background_key(password, key, kdf_rounds); ++ crypto::chacha20(keys_file_data.account_data.data(), keys_file_data.account_data.size(), key, keys_file_data.iv, &account_data[0]); ++ const bool is_background_wallet = json.Parse(account_data.c_str()).HasParseError() && json.IsObject(); ++ no_spend_key = no_spend_key || is_background_wallet; ++ } ++ } ++ + // The contents should be JSON if the wallet follows the new format. + if (json.Parse(account_data.c_str()).HasParseError()) + { +@@ -5184,6 +5436,7 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip + r = r && hwdev.verify_keys(keys.m_view_secret_key, keys.m_account_address.m_view_public_key); + if(!no_spend_key) + r = r && hwdev.verify_keys(keys.m_spend_secret_key, keys.m_account_address.m_spend_public_key); ++ spend_key_out = (!no_spend_key && r) ? keys.m_spend_secret_key : crypto::null_skey; + return r; + } + +@@ -5195,9 +5448,7 @@ void wallet2::encrypt_keys(const crypto::chacha_key &key) + + void wallet2::decrypt_keys(const crypto::chacha_key &key) + { +- // We use m_cache_key as a deterministic test to see if given key corresponds to original password +- const crypto::chacha_key cache_key = derive_cache_key(key); +- THROW_WALLET_EXCEPTION_IF(cache_key != m_cache_key, error::invalid_password); ++ verify_password_with_cached_key(key); + + m_account.encrypt_viewkey(key); + m_account.decrypt_keys(key); +@@ -5915,11 +6166,30 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin + { + if (wallet_name.empty()) + return; ++ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, ++ "cannot change wallet settings from background wallet"); + prepare_file_names(wallet_name); + boost::system::error_code ignored_ec; + THROW_WALLET_EXCEPTION_IF(!boost::filesystem::exists(m_keys_file, ignored_ec), error::file_not_found, m_keys_file); + bool r = store_keys(m_keys_file, password, m_watch_only); + THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); ++ ++ // Update the background keys file when we rewrite the main wallet keys file ++ if (m_background_sync_type == BackgroundSyncCustomPassword && m_custom_background_key) ++ { ++ const std::string background_keys_filename = make_background_keys_file_name(wallet_name); ++ if (!lock_background_keys_file(background_keys_filename)) ++ { ++ LOG_ERROR("Background keys file " << background_keys_filename << " is opened by another wallet program and cannot be rewritten"); ++ return; // not fatal, background keys file will just have different wallet settings ++ } ++ store_background_keys(m_custom_background_key.get()); ++ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); ++ } ++ else if (m_background_sync_type == BackgroundSyncReusePassword) ++ { ++ reset_background_sync_data(m_background_sync_data); ++ } + } + /*! + * \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there) +@@ -5953,6 +6223,16 @@ bool wallet2::wallet_valid_path_format(const std::string& file_path) + return !file_path.empty(); + } + //---------------------------------------------------------------------------------------------------- ++std::string wallet2::make_background_wallet_file_name(const std::string &wallet_file) ++{ ++ return wallet_file + config::BACKGROUND_WALLET_SUFFIX; ++} ++//---------------------------------------------------------------------------------------------------- ++std::string wallet2::make_background_keys_file_name(const std::string &wallet_file) ++{ ++ return make_background_wallet_file_name(wallet_file) + ".keys"; ++} ++//---------------------------------------------------------------------------------------------------- + bool wallet2::parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id) + { + cryptonote::blobdata payment_id_data; +@@ -6188,10 +6468,135 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass + THROW_WALLET_EXCEPTION_IF(true, error::file_read_error, "failed to load keys from buffer"); + } + +- wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only, password); ++ wallet_keys_unlocker unlocker(*this, m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only && !m_is_background_wallet, password); + + //keys loaded ok! + //try to load wallet cache. but even if we failed, it is not big problem ++ load_wallet_cache(use_fs, cache_buf); ++ ++ if (!m_persistent_rpc_client_id) ++ set_rpc_client_secret_key(rct::rct2sk(rct::skGen())); ++ ++ cryptonote::block genesis; ++ generate_genesis(genesis); ++ crypto::hash genesis_hash = get_block_hash(genesis); ++ ++ if (m_blockchain.empty()) ++ { ++ m_blockchain.push_back(genesis_hash); ++ m_last_block_reward = cryptonote::get_outs_money_amount(genesis.miner_tx); ++ } ++ else ++ { ++ check_genesis(genesis_hash); ++ } ++ ++ trim_hashchain(); ++ ++ if (get_num_subaddress_accounts() == 0) ++ add_subaddress_account(tr("Primary account")); ++ ++ try ++ { ++ find_and_save_rings(false); ++ } ++ catch (const std::exception &e) ++ { ++ MERROR("Failed to save rings, will try again next time"); ++ } ++ ++ try ++ { ++ if (use_fs) ++ m_message_store.read_from_file(get_multisig_wallet_state(), m_mms_file, m_load_deprecated_formats); ++ } ++ catch (const std::exception &e) ++ { ++ MERROR("Failed to initialize MMS, it will be unusable"); ++ } ++ ++ try ++ { ++ if (use_fs) ++ process_background_cache_on_open(); ++ } ++ catch (const std::exception &e) ++ { ++ MERROR("Failed to process background cache on open: " << e.what()); ++ } ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::process_background_cache_on_open() ++{ ++ if (m_wallet_file.empty()) ++ return; ++ if (m_background_syncing || m_is_background_wallet) ++ return; ++ if (m_background_sync_type == BackgroundSyncOff) ++ return; ++ if (m_background_sync_type == BackgroundSyncReusePassword) ++ { ++ const background_sync_data_t background_sync_data = m_background_sync_data; ++ const hashchain blockchain = m_blockchain; ++ process_background_cache(background_sync_data, blockchain, m_last_block_reward); ++ ++ // Reset the background cache after processing ++ reset_background_sync_data(m_background_sync_data); ++ } ++ else if (m_background_sync_type == BackgroundSyncCustomPassword) ++ { ++ // If the background wallet files don't exist, recreate them ++ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file); ++ const std::string background_wallet_file = make_background_wallet_file_name(m_wallet_file); ++ const bool background_keys_file_exists = boost::filesystem::exists(background_keys_file); ++ const bool background_wallet_exists = boost::filesystem::exists(background_wallet_file); ++ ++ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_keys_file), error::background_wallet_already_open, background_wallet_file); ++ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); ++ ++ if (!background_keys_file_exists) ++ { ++ MDEBUG("Background keys file not found, restoring"); ++ store_background_keys(m_custom_background_key.get()); ++ } ++ ++ if (!background_wallet_exists) ++ { ++ MDEBUG("Background cache not found, restoring"); ++ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); ++ return; ++ } ++ ++ MDEBUG("Loading background cache"); ++ ++ // Set up a minimal background wallet2 instance ++ std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype)); ++ background_w2->m_is_background_wallet = true; ++ background_w2->m_background_syncing = true; ++ background_w2->m_background_sync_type = m_background_sync_type; ++ background_w2->m_custom_background_key = m_custom_background_key; ++ ++ cryptonote::account_base account = m_account; ++ account.forget_spend_key(); ++ background_w2->m_account = account; ++ ++ // Load background cache from file ++ background_w2->clear(); ++ background_w2->prepare_file_names(background_wallet_file); ++ background_w2->load_wallet_cache(true/*use_fs*/); ++ ++ process_background_cache(background_w2->m_background_sync_data, background_w2->m_blockchain, background_w2->m_last_block_reward); ++ ++ // Reset the background cache after processing ++ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); ++ } else { ++ THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); ++ } ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::load_wallet_cache(const bool use_fs, const std::string& cache_buf) ++{ ++ boost::system::error_code e; + bool cache_missing = use_fs ? (!boost::filesystem::exists(m_wallet_file, e) || e) : cache_buf.empty(); + if (cache_missing) + { +@@ -6205,7 +6610,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass + bool r = true; + if (use_fs) + { +- load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max()); ++ r = load_from_file(m_wallet_file, cache_file_buf, std::numeric_limits<size_t>::max()); + THROW_WALLET_EXCEPTION_IF(!r, error::file_read_error, m_wallet_file); + } + +@@ -6218,7 +6623,7 @@ void wallet2::load(const std::string& wallet_, const epee::wipeable_string& pass + THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "internal error: failed to deserialize \"" + m_wallet_file + '\"'); + std::string cache_data; + cache_data.resize(cache_file_data.cache_data.size()); +- crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), m_cache_key, cache_file_data.iv, &cache_data[0]); ++ crypto::chacha20(cache_file_data.cache_data.data(), cache_file_data.cache_data.size(), get_cache_key(), cache_file_data.iv, &cache_data[0]); + + try { + bool loaded = false; +@@ -6459,6 +6864,21 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas + } + } + } ++ else if (m_background_sync_type == BackgroundSyncCustomPassword && m_background_syncing && !m_is_background_wallet) ++ { ++ // We're background syncing, so store the wallet cache as a background cache ++ // keeping the background sync data ++ try ++ { ++ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); ++ store_background_cache(m_custom_background_key.get(), false/*do_reset_background_sync_data*/); ++ } ++ catch (const std::exception &e) ++ { ++ MERROR("Failed to store background cache while background syncing: " << e.what()); ++ } ++ return; ++ } + + // get wallet cache data + boost::optional<wallet2::cache_file_data> cache_file_data = get_cache_file_data(); +@@ -6552,6 +6972,22 @@ void wallet2::store_to(const std::string &path, const epee::wipeable_string &pas + // store should only exist if the MMS is really active + m_message_store.write_to_file(get_multisig_wallet_state(), m_mms_file); + } ++ ++ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_background_syncing && !m_is_background_wallet) ++ { ++ // Update the background wallet cache when we store the main wallet cache ++ // Note: if background syncing when this is called, it means the background ++ // wallet is open and was already stored above ++ try ++ { ++ THROW_WALLET_EXCEPTION_IF(!m_custom_background_key, error::wallet_internal_error, "Custom background key not set"); ++ store_background_cache(m_custom_background_key.get(), true/*do_reset_background_sync_data*/); ++ } ++ catch (const std::exception &e) ++ { ++ MERROR("Failed to update background cache: " << e.what()); ++ } ++ } + } + //---------------------------------------------------------------------------------------------------- + boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() +@@ -6569,7 +7005,7 @@ boost::optional<wallet2::cache_file_data> wallet2::get_cache_file_data() + std::string cipher; + cipher.resize(cache_file_data.get().cache_data.size()); + cache_file_data.get().iv = crypto::rand<crypto::chacha_iv>(); +- crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), m_cache_key, cache_file_data.get().iv, &cipher[0]); ++ crypto::chacha20(cache_file_data.get().cache_data.data(), cache_file_data.get().cache_data.size(), get_cache_key(), cache_file_data.get().iv, &cipher[0]); + cache_file_data.get().cache_data = cipher; + return cache_file_data; + } +@@ -8645,6 +9081,34 @@ bool wallet2::is_keys_file_locked() const + return m_keys_file_locker->locked(); + } + ++bool wallet2::lock_background_keys_file(const std::string &background_keys_file) ++{ ++ if (background_keys_file.empty() || !boost::filesystem::exists(background_keys_file)) ++ return true; ++ if (m_background_keys_file_locker && m_background_keys_file_locker->locked()) ++ return true; ++ m_background_keys_file_locker.reset(new tools::file_locker(background_keys_file)); ++ return m_background_keys_file_locker->locked(); ++} ++ ++bool wallet2::unlock_background_keys_file() ++{ ++ if (!m_background_keys_file_locker) ++ { ++ MDEBUG("background keys file locker is not set"); ++ return false; ++ } ++ m_background_keys_file_locker.reset(); ++ return true; ++} ++ ++bool wallet2::is_background_keys_file_locked() const ++{ ++ if (!m_background_keys_file_locker) ++ return false; ++ return m_background_keys_file_locker->locked(); ++} ++ + bool wallet2::tx_add_fake_output(std::vector<std::vector<tools::wallet2::get_outs_entry>> &outs, uint64_t global_index, const crypto::public_key& output_public_key, const rct::key& mask, uint64_t real_index, bool unlocked, std::unordered_set<crypto::public_key> &valid_public_keys_cache) const + { + if (!unlocked) // don't add locked outs +@@ -13980,6 +14444,408 @@ bool wallet2::import_key_images(signed_tx_set & signed_tx, size_t offset, bool o + return import_key_images(signed_tx.key_images, offset, only_selected_transfers ? boost::make_optional(selected_transfers) : boost::none); + } + ++/* ++ In background sync mode, we use just the view key when the wallet is scanning ++ to identify all txs where: ++ ++ 1. We received an output. ++ 2. We spent an output. ++ 3. We *may* have spent a received output but we didn't know for sure because ++ the spend key was not loaded while background sync was enabled. ++ ++ When the user is ready to use the spend key again, we call this function to ++ process all those background synced transactions with the spend key loaded, ++ so that we can properly generate key images for the transactions which we ++ we were not able to do so for while background sync was enabled. This allows ++ us to determine *all* receives and spends the user completed while the wallet ++ had background sync enabled. Once this function completes, we can continue ++ scanning from where the background sync left off. ++ ++ Txs of type 3 (txs which we *may* have spent received output(s)) are txs where ++ 1+ rings contain an output that the user received and the wallet does not know ++ the associated key image for that output. We don't know if the user spent in ++ this type of tx or not. This function will generate key images for all outputs ++ we don't know key images for, and then check if those outputs were spent in ++ the txs of type 3. ++ ++ By storing this type of "plausible spend tx" when scanning in background sync ++ mode, we avoid the need to query the daemon with key images when background ++ sync mode is disabled to see if those key images were spent. This would ++ reveal key images to 3rd party nodes for users who don't run their own. ++ Although this is not a perfect solution to avoid revealing key images to a 3rd ++ party node (since tx submission trivially reveals key images to a node), it's ++ probably better than revealing *unused* key images to a 3rd party node, which ++ would enable the 3rd party to deduce that a tx is spending an output at least ++ X old when the key image is included in the chain. ++*/ ++void wallet2::process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_synced_chain, uint64_t last_block_reward) ++{ ++ // We expect the spend key to be in a decrypted state while ++ // m_processing_background_cache is true ++ m_processing_background_cache = true; ++ auto done_processing = epee::misc_utils::create_scope_leave_handler([&, this]() { ++ m_processing_background_cache = false; ++ }); ++ ++ if (m_background_syncing || m_multisig || m_watch_only || key_on_device()) ++ return; ++ ++ if (!background_sync_data.first_refresh_done) ++ { ++ MDEBUG("Skipping processing background cache, background cache has not synced yet"); ++ return; ++ } ++ ++ // Skip processing if wallet cache is synced higher than background cache ++ const uint64_t current_height = m_blockchain.size(); ++ const uint64_t background_height = background_synced_chain.size(); ++ MDEBUG("Background cache height " << background_height << " , wallet height " << current_height); ++ if (current_height > background_height) ++ { ++ MWARNING("Skipping processing background cache, synced height is higher than background cache"); ++ return; ++ } ++ ++ if (m_refresh_from_block_height < background_sync_data.wallet_refresh_from_block_height || ++ m_subaddress_lookahead_major > background_sync_data.subaddress_lookahead_major || ++ m_subaddress_lookahead_minor > background_sync_data.subaddress_lookahead_minor || ++ m_refresh_type < background_sync_data.wallet_refresh_type) ++ { ++ MWARNING("Skipping processing background cache, background wallet sync settings did not match main wallet's"); ++ MDEBUG("Wallet settings: " << ++ ", m_refresh_from_block_height: " << m_refresh_from_block_height << " vs " << background_sync_data.wallet_refresh_from_block_height << ++ ", m_subaddress_lookahead_major: " << m_subaddress_lookahead_major << " vs " << background_sync_data.subaddress_lookahead_major << ++ ", m_subaddress_lookahead_minor: " << m_subaddress_lookahead_minor << " vs " << background_sync_data.subaddress_lookahead_minor << ++ ", m_refresh_type: " << m_refresh_type << " vs " << background_sync_data.wallet_refresh_type); ++ return; ++ } ++ ++ // Sort background synced txs in the order they appeared in the cache so that ++ // we process them in the order they appeared in the chain. Thus if tx2 spends ++ // from tx1, we will know because tx1 is processed before tx2. ++ std::vector<std::pair<crypto::hash, background_synced_tx>> sorted_bgs_cache(background_sync_data.txs.begin(), background_sync_data.txs.end()); ++ std::sort(sorted_bgs_cache.begin(), sorted_bgs_cache.end(), ++ [](const std::pair<crypto::hash, background_synced_tx>& l, const std::pair<crypto::hash, background_synced_tx>& r) ++ { ++ uint64_t left_index = l.second.index_in_background_sync_data; ++ uint64_t right_index = r.second.index_in_background_sync_data; ++ THROW_WALLET_EXCEPTION_IF( ++ (left_index < right_index && l.second.height > r.second.height) || ++ (left_index > right_index && l.second.height < r.second.height), ++ error::wallet_internal_error, "Unexpected background sync data order"); ++ return left_index < right_index; ++ }); ++ ++ // All txs in the background cache should have height >= sync start height, ++ // but not fatal if not ++ if (!sorted_bgs_cache.empty() && sorted_bgs_cache[0].second.height < background_sync_data.start_height) ++ MWARNING("First tx in background cache has height (" << sorted_bgs_cache[0].second.height << ") lower than sync start height (" << background_sync_data.start_height << ")"); ++ ++ // We want to process all background synced txs in order to make sure ++ // the wallet state updates correctly. First we remove all txs from the wallet ++ // from before the background sync start height, then re-process them in ++ // chronological order. The background cache should contain a superset of ++ // *all* the wallet's txs from after the background sync start height. ++ MDEBUG("Processing " << background_sync_data.txs.size() << " background synced txs starting from height " << background_sync_data.start_height); ++ detached_blockchain_data dbd = detach_blockchain(background_sync_data.start_height); ++ ++ for (const auto &bgs_tx : sorted_bgs_cache) ++ { ++ MDEBUG("Processing background synced tx " << bgs_tx.first); ++ ++ process_new_transaction(bgs_tx.first, bgs_tx.second.tx, bgs_tx.second.output_indices, bgs_tx.second.height, 0, bgs_tx.second.block_timestamp, ++ cryptonote::is_coinbase(bgs_tx.second.tx), false/*pool*/, bgs_tx.second.double_spend_seen, {}, {}, true/*ignore_callbacks*/); ++ ++ // Re-set destination addresses if they were previously set ++ if (m_confirmed_txs.find(bgs_tx.first) != m_confirmed_txs.end() && ++ dbd.detached_confirmed_txs_dests.find(bgs_tx.first) != dbd.detached_confirmed_txs_dests.end()) ++ { ++ m_confirmed_txs[bgs_tx.first].m_dests = std::move(dbd.detached_confirmed_txs_dests[bgs_tx.first]); ++ } ++ } ++ ++ m_blockchain = background_synced_chain; ++ m_last_block_reward = last_block_reward; ++ ++ MDEBUG("Finished processing background sync data"); ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::reset_background_sync_data(background_sync_data_t &background_sync_data) ++{ ++ background_sync_data.first_refresh_done = false; ++ background_sync_data.start_height = get_blockchain_current_height(); ++ background_sync_data.txs.clear(); ++ ++ background_sync_data.wallet_refresh_from_block_height = m_refresh_from_block_height; ++ background_sync_data.subaddress_lookahead_major = m_subaddress_lookahead_major; ++ background_sync_data.subaddress_lookahead_minor = m_subaddress_lookahead_minor; ++ background_sync_data.wallet_refresh_type = m_refresh_type; ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data) ++{ ++ MDEBUG("Storing background cache (do_reset_background_sync_data=" << do_reset_background_sync_data << ")"); ++ ++ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error, ++ "Can only write a background cache when using a custom background password"); ++ ++ std::unique_ptr<wallet2> background_w2(new wallet2(m_nettype)); ++ background_w2->prepare_file_names(make_background_wallet_file_name(m_wallet_file)); ++ ++ // Make sure background wallet is opened by this wallet ++ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(background_w2->m_keys_file), ++ error::background_wallet_already_open, background_w2->m_wallet_file); ++ ++ // Load a background wallet2 instance using this wallet2 instance ++ std::string this_wallet2; ++ bool r = ::serialization::dump_binary(*this, this_wallet2); ++ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to serialize wallet cache"); ++ ++ background_w2->clear(); ++ r = ::serialization::parse_binary(this_wallet2, *background_w2); ++ THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to deserialize wallet cache"); ++ ++ // Clear sensitive data from background cache not needed to sync ++ background_w2->clear_user_data(); ++ ++ background_w2->m_is_background_wallet = true; ++ if (do_reset_background_sync_data) ++ reset_background_sync_data(background_w2->m_background_sync_data); ++ else ++ background_w2->m_background_sync_data = m_background_sync_data; ++ background_w2->m_background_syncing = true; ++ ++ background_w2->m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key); ++ background_w2->m_background_sync_type = m_background_sync_type; ++ background_w2->store(); ++ ++ MDEBUG("Background cache stored (" << background_w2->m_transfers.size() << " transfers, " << background_w2->m_background_sync_data.txs.size() << " background synced txs)"); ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::store_background_keys(const crypto::chacha_key &custom_background_key) ++{ ++ MDEBUG("Storing background keys"); ++ ++ const std::string background_keys_file = make_background_keys_file_name(m_wallet_file); ++ bool r = store_keys(background_keys_file, custom_background_key, false/*watch_only*/, true/*background_keys_file*/); ++ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, background_keys_file); ++ THROW_WALLET_EXCEPTION_IF(!is_background_keys_file_locked(), error::wallet_internal_error, background_keys_file + "\" should be locked"); ++ ++ // GUI uses the address file to differentiate non-mainnet wallets in the UI ++ const std::string background_address_file = make_background_wallet_file_name(m_wallet_file) + ".address.txt"; ++ if (m_nettype != MAINNET && !boost::filesystem::exists(background_address_file)) ++ { ++ r = save_to_file(background_address_file, m_account.get_public_address_str(m_nettype), true); ++ if (!r) MERROR("String with address text not saved"); ++ } ++ ++ MDEBUG("Background keys stored"); ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password) ++{ ++ MDEBUG("Storing background sync wallet"); ++ THROW_WALLET_EXCEPTION_IF(m_background_sync_type != BackgroundSyncCustomPassword, error::wallet_internal_error, ++ "Can only write a background sync wallet when using a custom background password"); ++ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, ++ "Can't write background sync wallet from an existing background cache"); ++ THROW_WALLET_EXCEPTION_IF(wallet_password == background_cache_password, ++ error::background_custom_password_same_as_wallet_password); ++ ++ // Set the background encryption key ++ crypto::chacha_key custom_background_key; ++ get_custom_background_key(background_cache_password, custom_background_key, m_kdf_rounds); ++ ++ // Keep the background encryption key in memory so the main wallet can update ++ // the background cache when it stores the main wallet cache ++ m_custom_background_key = boost::optional<crypto::chacha_key>(custom_background_key); ++ ++ if (m_wallet_file.empty() || m_keys_file.empty()) ++ return; ++ ++ // Save background keys file, then background cache, then update main wallet settings ++ store_background_keys(custom_background_key); ++ store_background_cache(custom_background_key, true/*do_reset_background_sync_data*/); ++ bool r = store_keys(m_keys_file, wallet_password, false/*watch_only*/); ++ THROW_WALLET_EXCEPTION_IF(!r, error::file_save_error, m_keys_file); ++ ++ MDEBUG("Background sync wallet saved successfully"); ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password) ++{ ++ MDEBUG("Setting background sync to type " << background_sync_type); ++ THROW_WALLET_EXCEPTION_IF(m_background_syncing || m_is_background_wallet, error::wallet_internal_error, ++ "Can't set background sync type from an existing background cache"); ++ verify_password_with_cached_key(wallet_password); ++ ++ if (background_sync_type != BackgroundSyncOff) ++ validate_background_cache_password_usage(background_sync_type, background_cache_password, m_multisig, m_watch_only, key_on_device()); ++ ++ THROW_WALLET_EXCEPTION_IF(background_sync_type == BackgroundSyncCustomPassword && wallet_password == background_cache_password, ++ error::background_custom_password_same_as_wallet_password); ++ ++ if (m_background_sync_type == background_sync_type && background_sync_type != BackgroundSyncCustomPassword) ++ return; // No need to make any changes ++ ++ if (!m_wallet_file.empty()) ++ { ++ // Delete existing background files if they already exist ++ const std::string old_background_wallet_file = make_background_wallet_file_name(m_wallet_file); ++ const std::string old_background_keys_file = make_background_keys_file_name(m_wallet_file); ++ const std::string old_background_address_file = old_background_wallet_file + ".address.txt"; ++ ++ // Make sure no other program is using the background wallet ++ THROW_WALLET_EXCEPTION_IF(!lock_background_keys_file(old_background_keys_file), ++ error::background_wallet_already_open, old_background_wallet_file); ++ ++ if (boost::filesystem::exists(old_background_wallet_file)) ++ if (!boost::filesystem::remove(old_background_wallet_file)) ++ LOG_ERROR("Error deleting background wallet file: " << old_background_wallet_file); ++ ++ if (boost::filesystem::exists(old_background_keys_file)) ++ if (!boost::filesystem::remove(old_background_keys_file)) ++ LOG_ERROR("Error deleting background keys file: " << old_background_keys_file); ++ ++ if (boost::filesystem::exists(old_background_address_file)) ++ if (!boost::filesystem::remove(old_background_address_file)) ++ LOG_ERROR("Error deleting background address file: " << old_background_address_file); ++ } ++ ++ m_background_sync_type = background_sync_type; ++ m_custom_background_key = boost::none; ++ ++ // Write the new files ++ switch (background_sync_type) ++ { ++ case BackgroundSyncOff: ++ case BackgroundSyncReusePassword: rewrite(m_wallet_file, wallet_password); break; ++ case BackgroundSyncCustomPassword: write_background_sync_wallet(wallet_password, background_cache_password.get()); break; ++ default: THROW_WALLET_EXCEPTION(error::wallet_internal_error, "unknown background sync type"); ++ } ++ ++ MDEBUG("Done setting background sync type"); ++} ++//---------------------------------------------------------------------------------------------------- ++/* ++ When background syncing, the wallet scans using just the view key, without ++ keeping the spend key in decrypted state. When a user returns to the wallet ++ and decrypts the spend key, the wallet processes the background synced txs, ++ then the wallet picks up scanning normally right where the background sync ++ left off. ++*/ ++void wallet2::start_background_sync() ++{ ++ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error, ++ "must setup background sync first before using background sync"); ++ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error, ++ "Can't start background syncing from a background wallet (it is always background syncing)"); ++ ++ MDEBUG("Starting background sync"); ++ ++ if (m_background_syncing) ++ { ++ MDEBUG("Already background syncing"); ++ return; ++ } ++ ++ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty()) ++ { ++ // Save the current state of the wallet cache. Only necessary when using a ++ // custom background password which uses distinct background wallet to sync. ++ // When reusing wallet password to sync we reuse the main wallet cache. ++ store(); ++ ++ // Wipe user data from the background wallet cache not needed to sync. ++ // Only wipe user data from background cache if wallet cache is stored ++ // on disk; otherwise we could lose the data. ++ clear_user_data(); ++ ++ // Wipe m_cache_key since it can be used to decrypt main wallet cache ++ m_cache_key.scrub(); ++ } ++ ++ reset_background_sync_data(m_background_sync_data); ++ m_background_syncing = true; ++ ++ // Wipe the spend key from memory ++ m_account.forget_spend_key(); ++ ++ MDEBUG("Background sync started at height " << m_background_sync_data.start_height); ++} ++//---------------------------------------------------------------------------------------------------- ++void wallet2::stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key) ++{ ++ MDEBUG("Stopping background sync"); ++ ++ // Verify provided password and spend secret key. If no spend secret key is ++ // provided, recover it from the wallet keys file ++ crypto::secret_key recovered_spend_key = crypto::null_skey; ++ if (!m_wallet_file.empty()) ++ { ++ THROW_WALLET_EXCEPTION_IF(!verify_password(wallet_password, recovered_spend_key), error::invalid_password); ++ } ++ else ++ { ++ verify_password_with_cached_key(wallet_password); ++ } ++ ++ if (spend_secret_key != crypto::null_skey) ++ { ++ THROW_WALLET_EXCEPTION_IF(!m_wallet_file.empty() && spend_secret_key != recovered_spend_key, ++ error::invalid_spend_key); ++ MDEBUG("Setting spend secret key with the provided key"); ++ recovered_spend_key = spend_secret_key; ++ } ++ ++ // Verify private spend key derives to wallet's public spend key ++ const auto verify_spend_key = [this](crypto::secret_key &recovered_spend_key) -> bool ++ { ++ crypto::public_key spend_public_key; ++ return recovered_spend_key != crypto::null_skey && ++ crypto::secret_key_to_public_key(recovered_spend_key, spend_public_key) && ++ m_account.get_keys().m_account_address.m_spend_public_key == spend_public_key; ++ }; ++ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key); ++ ++ THROW_WALLET_EXCEPTION_IF(m_background_sync_type == BackgroundSyncOff, error::wallet_internal_error, ++ "must setup background sync first before using background sync"); ++ THROW_WALLET_EXCEPTION_IF(m_is_background_wallet, error::wallet_internal_error, ++ "Can't stop background syncing from a background wallet"); ++ ++ if (!m_background_syncing) ++ return; ++ ++ // Copy background cache, we're about to overwrite it ++ const background_sync_data_t background_sync_data = m_background_sync_data; ++ const hashchain background_synced_chain = m_blockchain; ++ const uint64_t last_block_reward = m_last_block_reward; ++ ++ if (m_background_sync_type == BackgroundSyncCustomPassword && !m_wallet_file.empty()) ++ { ++ // Reload the wallet from disk ++ load(m_wallet_file, wallet_password); ++ THROW_WALLET_EXCEPTION_IF(!verify_spend_key(recovered_spend_key), error::invalid_spend_key); ++ } ++ m_background_syncing = false; ++ ++ // Set the plaintext spend key ++ m_account.set_spend_key(recovered_spend_key); ++ ++ // Encrypt the spend key when done if needed ++ epee::misc_utils::auto_scope_leave_caller keys_reencryptor; ++ if (m_ask_password == AskPasswordToDecrypt && !m_unattended && !m_watch_only) ++ keys_reencryptor = epee::misc_utils::create_scope_leave_handler([&, this]{encrypt_keys(wallet_password);}); ++ ++ // Now we can use the decrypted spend key to process background cache ++ process_background_cache(background_sync_data, background_synced_chain, last_block_reward); ++ ++ // Reset the background cache after processing ++ reset_background_sync_data(m_background_sync_data); ++ ++ MDEBUG("Background sync stopped"); ++} ++//---------------------------------------------------------------------------------------------------- + wallet2::payment_container wallet2::export_payments() const + { + payment_container payments; +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index b540eff6b..bfe3a4f19 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -257,6 +257,20 @@ private: + BackgroundMiningNo = 2, + }; + ++ enum BackgroundSyncType { ++ BackgroundSyncOff = 0, ++ BackgroundSyncReusePassword = 1, ++ BackgroundSyncCustomPassword = 2, ++ }; ++ ++ static BackgroundSyncType background_sync_type_from_str(const std::string &background_sync_type_str) ++ { ++ if (background_sync_type_str == "off") return BackgroundSyncOff; ++ if (background_sync_type_str == "reuse-wallet-password") return BackgroundSyncReusePassword; ++ if (background_sync_type_str == "custom-background-password") return BackgroundSyncCustomPassword; ++ throw std::logic_error("Unknown background sync type"); ++ }; ++ + enum ExportFormat { + Binary = 0, + Ascii, +@@ -283,7 +297,12 @@ private: + //! Just parses variables. + static std::unique_ptr<wallet2> make_dummy(const boost::program_options::variables_map& vm, bool unattended, const std::function<boost::optional<password_container>(const char *, bool)> &password_prompter); + +- static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds); ++ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds) ++ { ++ crypto::secret_key spend_key = crypto::null_skey; ++ return verify_password(keys_file_name, password, no_spend_key, hwdev, kdf_rounds, spend_key); ++ }; ++ static bool verify_password(const std::string& keys_file_name, const epee::wipeable_string& password, bool no_spend_key, hw::device &hwdev, uint64_t kdf_rounds, crypto::secret_key &spend_key_out); + static bool query_device(hw::device::device_type& device_type, const std::string& keys_file_name, const epee::wipeable_string& password, uint64_t kdf_rounds = 1); + + wallet2(cryptonote::network_type nettype = cryptonote::MAINNET, uint64_t kdf_rounds = 1, bool unattended = false, std::unique_ptr<epee::net_utils::http::http_client_factory> http_client_factory = std::unique_ptr<epee::net_utils::http::http_client_factory>(new net::http::client_factory())); +@@ -793,6 +812,54 @@ private: + END_SERIALIZE() + }; + ++ struct background_synced_tx ++ { ++ uint64_t index_in_background_sync_data; ++ cryptonote::transaction tx; ++ std::vector<uint64_t> output_indices; ++ uint64_t height; ++ uint64_t block_timestamp; ++ bool double_spend_seen; ++ ++ BEGIN_SERIALIZE_OBJECT() ++ VERSION_FIELD(0) ++ VARINT_FIELD(index_in_background_sync_data) ++ ++ // prune tx; don't need to keep signature data ++ if (!tx.serialize_base(ar)) ++ return false; ++ ++ FIELD(output_indices) ++ VARINT_FIELD(height) ++ VARINT_FIELD(block_timestamp) ++ FIELD(double_spend_seen) ++ END_SERIALIZE() ++ }; ++ ++ struct background_sync_data_t ++ { ++ bool first_refresh_done; ++ uint64_t start_height; ++ serializable_unordered_map<crypto::hash, background_synced_tx> txs; ++ ++ // Relevant wallet settings ++ uint64_t wallet_refresh_from_block_height; ++ size_t subaddress_lookahead_major; ++ size_t subaddress_lookahead_minor; ++ RefreshType wallet_refresh_type; ++ ++ BEGIN_SERIALIZE_OBJECT() ++ VERSION_FIELD(0) ++ FIELD(first_refresh_done) ++ FIELD(start_height) ++ FIELD(txs) ++ FIELD(wallet_refresh_from_block_height) ++ VARINT_FIELD(subaddress_lookahead_major) ++ VARINT_FIELD(subaddress_lookahead_minor) ++ VARINT_FIELD(wallet_refresh_type) ++ END_SERIALIZE() ++ }; ++ + typedef std::tuple<uint64_t, crypto::public_key, rct::key> get_outs_entry; + + struct parsed_block +@@ -989,7 +1056,8 @@ private: + /*! + * \brief verifies given password is correct for default wallet keys file + */ +- bool verify_password(const epee::wipeable_string& password); ++ bool verify_password(const epee::wipeable_string& password) {crypto::secret_key key = crypto::null_skey; return verify_password(password, key);}; ++ bool verify_password(const epee::wipeable_string& password, crypto::secret_key &spend_key_out); + cryptonote::account_base& get_account(){return m_account;} + const cryptonote::account_base& get_account()const{return m_account;} + +@@ -1093,6 +1161,7 @@ private: + cryptonote::network_type nettype() const { return m_nettype; } + bool watch_only() const { return m_watch_only; } + bool multisig(bool *ready = NULL, uint32_t *threshold = NULL, uint32_t *total = NULL) const; ++ bool is_background_wallet() const { return m_is_background_wallet; } + bool has_multisig_partial_key_images() const; + bool has_unknown_key_images() const; + bool get_multisig_seed(epee::wipeable_string& seed, const epee::wipeable_string &passphrase = std::string()) const; +@@ -1300,6 +1369,11 @@ private: + return; + } + a & m_has_ever_refreshed_from_node; ++ if ((m_background_sync_type == BackgroundSyncCustomPassword && m_is_background_wallet) || ++ (m_background_sync_type == BackgroundSyncReusePassword && !m_is_background_wallet)) ++ { ++ a & m_background_sync_data; ++ } + } + + BEGIN_SERIALIZE_OBJECT() +@@ -1336,6 +1410,11 @@ private: + return true; + } + FIELD(m_has_ever_refreshed_from_node) ++ if ((m_background_sync_type == BackgroundSyncCustomPassword && m_is_background_wallet) || ++ (m_background_sync_type == BackgroundSyncReusePassword && !m_is_background_wallet)) ++ { ++ FIELD(m_background_sync_data) ++ } + END_SERIALIZE() + + /*! +@@ -1351,6 +1430,8 @@ private: + * \return Whether path is valid format + */ + static bool wallet_valid_path_format(const std::string& file_path); ++ static std::string make_background_wallet_file_name(const std::string &wallet_file); ++ static std::string make_background_keys_file_name(const std::string &wallet_file); + static bool parse_long_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); + static bool parse_short_payment_id(const std::string& payment_id_str, crypto::hash8& payment_id); + static bool parse_payment_id(const std::string& payment_id_str, crypto::hash& payment_id); +@@ -1399,6 +1480,9 @@ private: + void ignore_outputs_below(uint64_t value) { m_ignore_outputs_below = value; } + bool track_uses() const { return m_track_uses; } + void track_uses(bool value) { m_track_uses = value; } ++ BackgroundSyncType background_sync_type() const { return m_background_sync_type; } ++ void setup_background_sync(BackgroundSyncType background_sync_type, const epee::wipeable_string &wallet_password, const boost::optional<epee::wipeable_string> &background_cache_password); ++ bool is_background_syncing() const { return m_background_syncing; } + bool show_wallet_name_when_locked() const { return m_show_wallet_name_when_locked; } + void show_wallet_name_when_locked(bool value) { m_show_wallet_name_when_locked = value; } + BackgroundMiningSetupType setup_background_mining() const { return m_setup_background_mining; } +@@ -1425,6 +1509,7 @@ private: + void enable_multisig(bool enable) { m_enable_multisig = enable; } + bool is_mismatched_daemon_version_allowed() const { return m_allow_mismatched_daemon_version; } + void allow_mismatched_daemon_version(bool allow_mismatch) { m_allow_mismatched_daemon_version = allow_mismatch; } ++ uint64_t kdf_rounds() const { return m_kdf_rounds; }; + + bool get_tx_key_cached(const crypto::hash &txid, crypto::secret_key &tx_key, std::vector<crypto::secret_key> &additional_tx_keys) const; + void set_tx_key(const crypto::hash &txid, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const boost::optional<cryptonote::account_public_address> &single_destination_subaddress = boost::none); +@@ -1714,6 +1799,9 @@ private: + uint64_t get_bytes_sent() const; + uint64_t get_bytes_received() const; + ++ void start_background_sync(); ++ void stop_background_sync(const epee::wipeable_string &wallet_password, const crypto::secret_key &spend_secret_key = crypto::null_skey); ++ + // MMS ------------------------------------------------------------------------------------------------- + mms::message_store& get_message_store() { return m_message_store; }; + const mms::message_store& get_message_store() const { return m_message_store; }; +@@ -1749,6 +1837,9 @@ private: + * \return Whether it was successful. + */ + bool store_keys(const std::string& keys_file_name, const epee::wipeable_string& password, bool watch_only = false); ++ bool store_keys(const std::string& keys_file_name, const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false); ++ boost::optional<wallet2::keys_file_data> get_keys_file_data(const crypto::chacha_key& key, bool watch_only = false, bool background_keys_file = false); ++ bool store_keys_file_data(const std::string& keys_file_name, wallet2::keys_file_data &keys_file_data, bool background_keys_file = false); + /*! + * \brief Load wallet keys information from wallet file. + * \param keys_file_name Name of wallet file +@@ -1762,6 +1853,7 @@ private: + */ + bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password); + bool load_keys_buf(const std::string& keys_buf, const epee::wipeable_string& password, boost::optional<crypto::chacha_key>& keys_to_encrypt); ++ void load_wallet_cache(const bool use_fs, const std::string& cache_buf = ""); + void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint8_t block_version, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL, bool ignore_callbacks = false); + bool should_skip_block(const cryptonote::block &b, uint64_t height) const; + void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL); +@@ -1770,6 +1862,7 @@ private: + void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const; + bool clear(); + void clear_soft(bool keep_key_images=false); ++ void clear_user_data(); + void pull_blocks(bool first, bool try_incremental, uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<cryptonote::block_complete_entry> &blocks, std::vector<cryptonote::COMMAND_RPC_GET_BLOCKS_FAST::block_output_indices> &o_indices, uint64_t ¤t_height); + void pull_hashes(uint64_t start_height, uint64_t& blocks_start_height, const std::list<crypto::hash> &short_chain_history, std::vector<crypto::hash> &hashes); + void fast_refresh(uint64_t stop_height, uint64_t &blocks_start_height, std::list<crypto::hash> &short_chain_history, bool force = false); +@@ -1821,10 +1914,23 @@ private: + bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs); + crypto::chacha_key get_ringdb_key(); + void setup_keys(const epee::wipeable_string &password); ++ const crypto::chacha_key get_cache_key(); ++ void verify_password_with_cached_key(const epee::wipeable_string &password); ++ void verify_password_with_cached_key(const crypto::chacha_key &key); + size_t get_transfer_details(const crypto::key_image &ki) const; + tx_entry_data get_tx_entries(const std::unordered_set<crypto::hash> &txids); + void sort_scan_tx_entries(std::vector<process_tx_entry_t> &unsorted_tx_entries); + void process_scan_txs(const tx_entry_data &txs_to_scan, const tx_entry_data &txs_to_reprocess, const std::unordered_set<crypto::hash> &tx_hashes_to_reprocess, detached_blockchain_data &dbd); ++ void write_background_sync_wallet(const epee::wipeable_string &wallet_password, const epee::wipeable_string &background_cache_password); ++ void process_background_cache_on_open(); ++ void process_background_cache(const background_sync_data_t &background_sync_data, const hashchain &background_chain, uint64_t last_block_reward); ++ void reset_background_sync_data(background_sync_data_t &background_sync_data); ++ void store_background_cache(const crypto::chacha_key &custom_background_key, const bool do_reset_background_sync_data = true); ++ void store_background_keys(const crypto::chacha_key &custom_background_key); ++ ++ bool lock_background_keys_file(const std::string &background_keys_file); ++ bool unlock_background_keys_file(); ++ bool is_background_keys_file_locked() const; + + void register_devices(); + hw::device& lookup_device(const std::string & device_descriptor); +@@ -1940,6 +2046,11 @@ private: + uint64_t m_ignore_outputs_above; + uint64_t m_ignore_outputs_below; + bool m_track_uses; ++ bool m_is_background_wallet; ++ BackgroundSyncType m_background_sync_type; ++ bool m_background_syncing; ++ bool m_processing_background_cache; ++ background_sync_data_t m_background_sync_data; + bool m_show_wallet_name_when_locked; + uint32_t m_inactivity_lock_timeout; + BackgroundMiningSetupType m_setup_background_mining; +@@ -1985,6 +2096,7 @@ private: + + uint64_t m_last_block_reward; + std::unique_ptr<tools::file_locker> m_keys_file_locker; ++ std::unique_ptr<tools::file_locker> m_background_keys_file_locker; + + mms::message_store m_message_store; + bool m_original_keys_available; +@@ -1992,6 +2104,7 @@ private: + crypto::secret_key m_original_view_secret_key; + + crypto::chacha_key m_cache_key; ++ boost::optional<crypto::chacha_key> m_custom_background_key = boost::none; + std::shared_ptr<wallet_keys_unlocker> m_encrypt_keys_after_refresh; + + bool m_unattended; +@@ -2025,6 +2138,8 @@ BOOST_CLASS_VERSION(tools::wallet2::signed_tx_set, 1) + BOOST_CLASS_VERSION(tools::wallet2::tx_construction_data, 4) + BOOST_CLASS_VERSION(tools::wallet2::pending_tx, 3) + BOOST_CLASS_VERSION(tools::wallet2::multisig_sig, 1) ++BOOST_CLASS_VERSION(tools::wallet2::background_synced_tx, 0) ++BOOST_CLASS_VERSION(tools::wallet2::background_sync_data_t, 0) + + namespace boost + { +@@ -2523,6 +2638,29 @@ namespace boost + return; + a & x.multisig_sigs; + } ++ ++ template <class Archive> ++ inline void serialize(Archive& a, tools::wallet2::background_synced_tx &x, const boost::serialization::version_type ver) ++ { ++ a & x.index_in_background_sync_data; ++ a & x.tx; ++ a & x.output_indices; ++ a & x.height; ++ a & x.block_timestamp; ++ a & x.double_spend_seen; ++ } ++ ++ template <class Archive> ++ inline void serialize(Archive& a, tools::wallet2::background_sync_data_t &x, const boost::serialization::version_type ver) ++ { ++ a & x.first_refresh_done; ++ a & x.start_height; ++ a & x.txs.parent(); ++ a & x.wallet_refresh_from_block_height; ++ a & x.subaddress_lookahead_major; ++ a & x.subaddress_lookahead_minor; ++ a & x.wallet_refresh_type; ++ } + } + } + +diff --git a/src/wallet/wallet_errors.h b/src/wallet/wallet_errors.h +index 1f7e1c75d..d17b721a9 100644 +--- a/src/wallet/wallet_errors.h ++++ b/src/wallet/wallet_errors.h +@@ -63,6 +63,7 @@ namespace tools + // invalid_password + // invalid_priority + // invalid_multisig_seed ++ // invalid_spend_key + // refresh_error * + // acc_outs_lookup_error + // block_parse_error +@@ -96,6 +97,9 @@ namespace tools + // wallet_files_doesnt_correspond + // scan_tx_error * + // wont_reprocess_recent_txs_via_untrusted_daemon ++ // background_sync_error * ++ // background_wallet_already_open ++ // background_custom_password_same_as_wallet_password + // + // * - class with protected ctor + +@@ -303,6 +307,16 @@ namespace tools + std::string to_string() const { return wallet_logic_error::to_string(); } + }; + ++ struct invalid_spend_key : public wallet_logic_error ++ { ++ explicit invalid_spend_key(std::string&& loc) ++ : wallet_logic_error(std::move(loc), "invalid spend key") ++ { ++ } ++ ++ std::string to_string() const { return wallet_logic_error::to_string(); } ++ }; ++ + //---------------------------------------------------------------------------------------------------- + struct invalid_pregenerated_random : public wallet_logic_error + { +@@ -944,6 +958,31 @@ namespace tools + } + }; + //---------------------------------------------------------------------------------------------------- ++ struct background_sync_error : public wallet_logic_error ++ { ++ protected: ++ explicit background_sync_error(std::string&& loc, const std::string& message) ++ : wallet_logic_error(std::move(loc), message) ++ { ++ } ++ }; ++ //---------------------------------------------------------------------------------------------------- ++ struct background_wallet_already_open : public background_sync_error ++ { ++ explicit background_wallet_already_open(std::string&& loc, const std::string& background_wallet_file) ++ : background_sync_error(std::move(loc), "background wallet " + background_wallet_file + " is already opened by another wallet program") ++ { ++ } ++ }; ++ //---------------------------------------------------------------------------------------------------- ++ struct background_custom_password_same_as_wallet_password : public background_sync_error ++ { ++ explicit background_custom_password_same_as_wallet_password(std::string&& loc) ++ : background_sync_error(std::move(loc), "custom background password must be different than wallet password") ++ { ++ } ++ }; ++ //---------------------------------------------------------------------------------------------------- + + #if !defined(_MSC_VER) + +diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp +index 4e42d64cd..822fc828b 100644 +--- a/src/wallet/wallet_rpc_server.cpp ++++ b/src/wallet/wallet_rpc_server.cpp +@@ -73,6 +73,54 @@ using namespace epee; + } \ + } while(0) + ++#define CHECK_IF_BACKGROUND_SYNCING() \ ++ do \ ++ { \ ++ if (!m_wallet) { return not_open(er); } \ ++ if (m_wallet->is_background_wallet()) \ ++ { \ ++ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET; \ ++ er.message = "This command is disabled for background wallets."; \ ++ return false; \ ++ } \ ++ if (m_wallet->is_background_syncing()) \ ++ { \ ++ er.code = WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING; \ ++ er.message = "This command is disabled while background syncing. Stop background syncing to use this command."; \ ++ return false; \ ++ } \ ++ } while(0) ++ ++#define PRE_VALIDATE_BACKGROUND_SYNC() \ ++ do \ ++ { \ ++ if (!m_wallet) { return not_open(er); } \ ++ if (m_restricted) \ ++ { \ ++ er.code = WALLET_RPC_ERROR_CODE_DENIED; \ ++ er.message = "Command unavailable in restricted mode."; \ ++ return false; \ ++ } \ ++ if (m_wallet->key_on_device()) \ ++ { \ ++ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \ ++ er.message = "Command not supported by HW wallet"; \ ++ return false; \ ++ } \ ++ if (m_wallet->multisig()) \ ++ { \ ++ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; \ ++ er.message = "Multisig wallet cannot enable background sync"; \ ++ return false; \ ++ } \ ++ if (m_wallet->watch_only()) \ ++ { \ ++ er.code = WALLET_RPC_ERROR_CODE_WATCH_ONLY; \ ++ er.message = "Watch-only wallet cannot enable background sync"; \ ++ return false; \ ++ } \ ++ } while (0) ++ + namespace + { + const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"}; +@@ -291,6 +339,9 @@ namespace tools + { + if (!m_wallet) + return; ++ // Background mining can be toggled from the main wallet ++ if (m_wallet->is_background_wallet() || m_wallet->is_background_syncing()) ++ return; + + tools::wallet2::BackgroundMiningSetupType setup = m_wallet->setup_background_mining(); + if (setup == tools::wallet2::BackgroundMiningNo) +@@ -582,6 +633,7 @@ namespace tools + bool wallet_rpc_server::on_create_address(const wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_CREATE_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + if (req.count < 1 || req.count > 64) { +@@ -619,6 +671,7 @@ namespace tools + bool wallet_rpc_server::on_label_address(const wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::request& req, wallet_rpc::COMMAND_RPC_LABEL_ADDRESS::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->set_subaddress_label(req.index, req.label); +@@ -681,6 +734,7 @@ namespace tools + bool wallet_rpc_server::on_create_account(const wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_CREATE_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->add_subaddress_account(req.label); +@@ -698,6 +752,7 @@ namespace tools + bool wallet_rpc_server::on_label_account(const wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::request& req, wallet_rpc::COMMAND_RPC_LABEL_ACCOUNT::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->set_subaddress_label({req.account_index, 0}, req.label); +@@ -713,6 +768,7 @@ namespace tools + bool wallet_rpc_server::on_get_account_tags(const wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::request& req, wallet_rpc::COMMAND_RPC_GET_ACCOUNT_TAGS::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + const std::pair<std::map<std::string, std::string>, std::vector<std::string>> account_tags = m_wallet->get_account_tags(); + for (const std::pair<const std::string, std::string>& p : account_tags.first) + { +@@ -732,6 +788,7 @@ namespace tools + bool wallet_rpc_server::on_tag_accounts(const wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_TAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->set_account_tag(req.accounts, req.tag); +@@ -747,6 +804,7 @@ namespace tools + bool wallet_rpc_server::on_untag_accounts(const wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::request& req, wallet_rpc::COMMAND_RPC_UNTAG_ACCOUNTS::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->set_account_tag(req.accounts, ""); +@@ -762,6 +820,7 @@ namespace tools + bool wallet_rpc_server::on_set_account_tag_description(const wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::request& req, wallet_rpc::COMMAND_RPC_SET_ACCOUNT_TAG_DESCRIPTION::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->set_account_tag_description(req.tag, req.description); +@@ -792,6 +851,7 @@ namespace tools + bool wallet_rpc_server::on_freeze(const wallet_rpc::COMMAND_RPC_FREEZE::request& req, wallet_rpc::COMMAND_RPC_FREEZE::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + if (req.key_image.empty()) +@@ -820,6 +880,7 @@ namespace tools + bool wallet_rpc_server::on_thaw(const wallet_rpc::COMMAND_RPC_THAW::request& req, wallet_rpc::COMMAND_RPC_THAW::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + if (req.key_image.empty()) +@@ -848,6 +909,7 @@ namespace tools + bool wallet_rpc_server::on_frozen(const wallet_rpc::COMMAND_RPC_FROZEN::request& req, wallet_rpc::COMMAND_RPC_FROZEN::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + if (req.key_image.empty()) +@@ -875,6 +937,8 @@ namespace tools + //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::validate_transfer(const std::list<wallet_rpc::transfer_destination>& destinations, const std::string& payment_id, std::vector<cryptonote::tx_destination_entry>& dsts, std::vector<uint8_t>& extra, bool at_least_one_destination, epee::json_rpc::error& er) + { ++ CHECK_IF_BACKGROUND_SYNCING(); ++ + crypto::hash8 integrated_payment_id = crypto::null_hash8; + std::string extra_nonce; + for (auto it = destinations.begin(); it != destinations.end(); it++) +@@ -1192,6 +1256,7 @@ namespace tools + } + + CHECK_MULTISIG_ENABLED(); ++ CHECK_IF_BACKGROUND_SYNCING(); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.unsigned_txset, blob)) +@@ -1273,6 +1338,7 @@ namespace tools + er.message = "command not supported by watch-only wallet"; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + if(req.unsigned_txset.empty() && req.multisig_txset.empty()) + { + er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; +@@ -1542,6 +1608,7 @@ namespace tools + } + + CHECK_MULTISIG_ENABLED(); ++ CHECK_IF_BACKGROUND_SYNCING(); + + try + { +@@ -2091,6 +2158,7 @@ namespace tools + er.message = "The wallet is watch-only. Cannot retrieve seed."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + if (!m_wallet->is_deterministic()) + { + er.code = WALLET_RPC_ERROR_CODE_NON_DETERMINISTIC; +@@ -2119,6 +2187,7 @@ namespace tools + er.message = "The wallet is watch-only. Cannot retrieve spend key."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + epee::wipeable_string key = epee::to_hex::wipeable_string(m_wallet->get_account().get_keys().m_spend_secret_key); + res.key = std::string(key.data(), key.size()); + } +@@ -2140,6 +2209,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + try + { +@@ -2153,6 +2223,79 @@ namespace tools + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ ++ bool wallet_rpc_server::on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) ++ { ++ try ++ { ++ PRE_VALIDATE_BACKGROUND_SYNC(); ++ const tools::wallet2::BackgroundSyncType background_sync_type = tools::wallet2::background_sync_type_from_str(req.background_sync_type); ++ boost::optional<epee::wipeable_string> background_cache_password = boost::none; ++ if (background_sync_type == tools::wallet2::BackgroundSyncCustomPassword) ++ background_cache_password = boost::optional<epee::wipeable_string>(req.background_cache_password); ++ m_wallet->setup_background_sync(background_sync_type, req.wallet_password, background_cache_password); ++ } ++ catch (...) ++ { ++ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); ++ return false; ++ } ++ return true; ++ } ++ //------------------------------------------------------------------------------------------------------------------------------ ++ bool wallet_rpc_server::on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) ++ { ++ try ++ { ++ PRE_VALIDATE_BACKGROUND_SYNC(); ++ m_wallet->start_background_sync(); ++ } ++ catch (...) ++ { ++ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); ++ return false; ++ } ++ return true; ++ } ++ //------------------------------------------------------------------------------------------------------------------------------ ++ bool wallet_rpc_server::on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx) ++ { ++ try ++ { ++ PRE_VALIDATE_BACKGROUND_SYNC(); ++ crypto::secret_key spend_secret_key = crypto::null_skey; ++ ++ // Load the spend key from seed if seed is provided ++ if (!req.seed.empty()) ++ { ++ crypto::secret_key recovery_key; ++ std::string language; ++ ++ if (!crypto::ElectrumWords::words_to_bytes(req.seed, recovery_key, language)) ++ { ++ er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR; ++ er.message = "Electrum-style word list failed verification"; ++ return false; ++ } ++ ++ if (!req.seed_offset.empty()) ++ recovery_key = cryptonote::decrypt_key(recovery_key, req.seed_offset); ++ ++ // generate spend key ++ cryptonote::account_base account; ++ account.generate(recovery_key, true, false); ++ spend_secret_key = account.get_keys().m_spend_secret_key; ++ } ++ ++ m_wallet->stop_background_sync(req.wallet_password, spend_secret_key); ++ } ++ catch (...) ++ { ++ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR); ++ return false; ++ } ++ return true; ++ } ++ //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_sign(const wallet_rpc::COMMAND_RPC_SIGN::request& req, wallet_rpc::COMMAND_RPC_SIGN::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); +@@ -2162,6 +2305,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + tools::wallet2::message_signature_type_t signature_type = tools::wallet2::sign_with_spend_key; + if (req.signature_type == "spend" || req.signature_type == "") +@@ -2254,6 +2398,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + if (req.txids.size() != req.notes.size()) + { +@@ -2326,6 +2471,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + m_wallet->set_attribute(req.key, req.value); + +@@ -2353,6 +2499,7 @@ namespace tools + bool wallet_rpc_server::on_get_tx_key(const wallet_rpc::COMMAND_RPC_GET_TX_KEY::request& req, wallet_rpc::COMMAND_RPC_GET_TX_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) +@@ -2444,6 +2591,7 @@ namespace tools + bool wallet_rpc_server::on_get_tx_proof(const wallet_rpc::COMMAND_RPC_GET_TX_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_TX_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + + crypto::hash txid; + if (!epee::string_tools::hex_to_pod(req.txid, txid)) +@@ -2560,6 +2708,7 @@ namespace tools + bool wallet_rpc_server::on_get_reserve_proof(const wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::request& req, wallet_rpc::COMMAND_RPC_GET_RESERVE_PROOF::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + + boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; + if (!req.all) +@@ -2802,6 +2951,7 @@ namespace tools + er.message = "command not supported by HW wallet"; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + try + { +@@ -2831,6 +2981,7 @@ namespace tools + er.message = "command not supported by HW wallet"; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + cryptonote::blobdata blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(req.outputs_data_hex, blob)) +@@ -2856,6 +3007,7 @@ namespace tools + bool wallet_rpc_server::on_export_key_images(const wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::request& req, wallet_rpc::COMMAND_RPC_EXPORT_KEY_IMAGES::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + std::pair<uint64_t, std::vector<std::pair<crypto::key_image, crypto::signature>>> ski = m_wallet->export_key_images(req.all); +@@ -2892,6 +3044,7 @@ namespace tools + er.message = "This command requires a trusted daemon."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + std::vector<std::pair<crypto::key_image, crypto::signature>> ski; +@@ -2960,6 +3113,7 @@ namespace tools + bool wallet_rpc_server::on_get_address_book(const wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_GET_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx) + { + if (!m_wallet) return not_open(er); ++ CHECK_IF_BACKGROUND_SYNCING(); + const auto ab = m_wallet->get_address_book(); + if (req.entries.empty()) + { +@@ -3005,6 +3159,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + cryptonote::address_parse_info info; + er.message = ""; +@@ -3047,6 +3202,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + const auto ab = m_wallet->get_address_book(); + if (req.index >= ab.size()) +@@ -3109,6 +3265,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + const auto ab = m_wallet->get_address_book(); + if (req.index >= ab.size()) +@@ -3179,6 +3336,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + std::unordered_set<crypto::hash> txids; + std::list<std::string>::const_iterator i = req.txids.begin(); +@@ -3218,6 +3376,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + try + { + m_wallet->rescan_spent(); +@@ -3482,6 +3641,7 @@ namespace tools + er.message = "Command unavailable in restricted mode."; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + if (m_wallet->verify_password(req.old_password)) + { + try +@@ -4009,6 +4169,7 @@ namespace tools + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + res.multisig_info = m_wallet->get_multisig_first_kex_msg(); + return true; +@@ -4036,6 +4197,7 @@ namespace tools + er.message = "wallet is watch-only and cannot be made multisig"; + return false; + } ++ CHECK_IF_BACKGROUND_SYNCING(); + + try + { +diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h +index 3308d1751..c2329aafe 100644 +--- a/src/wallet/wallet_rpc_server.h ++++ b/src/wallet/wallet_rpc_server.h +@@ -160,6 +160,9 @@ namespace tools + MAP_JON_RPC_WE("set_log_categories", on_set_log_categories, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES) + MAP_JON_RPC_WE("estimate_tx_size_and_weight", on_estimate_tx_size_and_weight, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT) + MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) ++ MAP_JON_RPC_WE("setup_background_sync", on_setup_background_sync, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC) ++ MAP_JON_RPC_WE("start_background_sync", on_start_background_sync, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC) ++ MAP_JON_RPC_WE("stop_background_sync", on_stop_background_sync, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC) + END_JSON_RPC_MAP() + END_URI_MAP2() + +@@ -251,6 +254,9 @@ namespace tools + bool on_set_log_categories(const wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::request& req, wallet_rpc::COMMAND_RPC_SET_LOG_CATEGORIES::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_estimate_tx_size_and_weight(const wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::request& req, wallet_rpc::COMMAND_RPC_ESTIMATE_TX_SIZE_AND_WEIGHT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); ++ bool on_setup_background_sync(const wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_SETUP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); ++ bool on_start_background_sync(const wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_START_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); ++ bool on_stop_background_sync(const wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::request& req, wallet_rpc::COMMAND_RPC_STOP_BACKGROUND_SYNC::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); + + //json rpc v2 + bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL); +diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h +index b6098d95c..a44b56ed6 100644 +--- a/src/wallet/wallet_rpc_server_commands_defs.h ++++ b/src/wallet/wallet_rpc_server_commands_defs.h +@@ -2696,5 +2696,69 @@ namespace wallet_rpc + typedef epee::misc_utils::struct_init<response_t> response; + }; + ++ struct COMMAND_RPC_SETUP_BACKGROUND_SYNC ++ { ++ struct request_t ++ { ++ std::string background_sync_type; ++ std::string wallet_password; ++ std::string background_cache_password; ++ ++ BEGIN_KV_SERIALIZE_MAP() ++ KV_SERIALIZE(background_sync_type) ++ KV_SERIALIZE(wallet_password) ++ KV_SERIALIZE_OPT(background_cache_password, (std::string)"") ++ END_KV_SERIALIZE_MAP() ++ }; ++ typedef epee::misc_utils::struct_init<request_t> request; ++ ++ struct response_t ++ { ++ BEGIN_KV_SERIALIZE_MAP() ++ END_KV_SERIALIZE_MAP() ++ }; ++ typedef epee::misc_utils::struct_init<response_t> response; ++ }; ++ ++ struct COMMAND_RPC_START_BACKGROUND_SYNC ++ { ++ struct request_t ++ { ++ BEGIN_KV_SERIALIZE_MAP() ++ END_KV_SERIALIZE_MAP() ++ }; ++ typedef epee::misc_utils::struct_init<request_t> request; ++ ++ struct response_t ++ { ++ BEGIN_KV_SERIALIZE_MAP() ++ END_KV_SERIALIZE_MAP() ++ }; ++ typedef epee::misc_utils::struct_init<response_t> response; ++ }; ++ ++ struct COMMAND_RPC_STOP_BACKGROUND_SYNC ++ { ++ struct request_t ++ { ++ std::string wallet_password; ++ std::string seed; ++ std::string seed_offset; ++ ++ BEGIN_KV_SERIALIZE_MAP() ++ KV_SERIALIZE(wallet_password) ++ KV_SERIALIZE_OPT(seed, (std::string)"") ++ KV_SERIALIZE_OPT(seed_offset, (std::string)"") ++ END_KV_SERIALIZE_MAP() ++ }; ++ typedef epee::misc_utils::struct_init<request_t> request; ++ ++ struct response_t ++ { ++ BEGIN_KV_SERIALIZE_MAP() ++ END_KV_SERIALIZE_MAP() ++ }; ++ typedef epee::misc_utils::struct_init<response_t> response; ++ }; + } + } +diff --git a/src/wallet/wallet_rpc_server_error_codes.h b/src/wallet/wallet_rpc_server_error_codes.h +index 734229380..b964036bd 100644 +--- a/src/wallet/wallet_rpc_server_error_codes.h ++++ b/src/wallet/wallet_rpc_server_error_codes.h +@@ -79,3 +79,5 @@ + #define WALLET_RPC_ERROR_CODE_ZERO_AMOUNT -46 + #define WALLET_RPC_ERROR_CODE_INVALID_SIGNATURE_TYPE -47 + #define WALLET_RPC_ERROR_CODE_DISABLED -48 ++#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_WALLET -49 ++#define WALLET_RPC_ERROR_CODE_IS_BACKGROUND_SYNCING -50 +diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py +index 4063911f4..39ee86df3 100755 +--- a/tests/functional_tests/transfer.py ++++ b/tests/functional_tests/transfer.py +@@ -30,6 +30,7 @@ + + from __future__ import print_function + import json ++import util_resources + import pprint + from deepdiff import DeepDiff + pp = pprint.PrettyPrinter(indent=2) +@@ -46,6 +47,17 @@ seeds = [ + 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid', + ] + ++def diff_transfers(actual_transfers, expected_transfers, ignore_order = True): ++ # The payments containers aren't ordered; re-scanning can lead to diff orders ++ diff = DeepDiff(actual_transfers, expected_transfers, ignore_order = ignore_order) ++ if diff != {}: ++ pp.pprint(diff) ++ assert diff == {} ++ ++def diff_incoming_transfers(actual_transfers, expected_transfers): ++ # wallet2 m_transfers container is ordered and order should be the same across rescans ++ diff_transfers(actual_transfers, expected_transfers, ignore_order = False) ++ + class TransferTest(): + def run_test(self): + self.reset() +@@ -63,6 +75,8 @@ class TransferTest(): + self.check_is_key_image_spent() + self.check_scan_tx() + self.check_subtract_fee_from_outputs() ++ self.check_background_sync() ++ self.check_background_sync_reorg_recovery() + + def reset(self): + print('Resetting blockchain') +@@ -71,6 +85,387 @@ class TransferTest(): + daemon.pop_blocks(res.height - 1) + daemon.flush_txpool() + ++ def check_background_sync(self): ++ daemon = Daemon() ++ ++ print('Testing background sync') ++ ++ # Background sync type options ++ REUSE_PASSWORD = 'reuse-wallet-password' ++ CUSTOM_PASSWORD = 'custom-background-password' ++ ++ # Some helper functions ++ def stop_with_wrong_inputs(wallet, wallet_password, seed = ''): ++ invalid = False ++ try: wallet.stop_background_sync(wallet_password = wallet_password, seed = seed) ++ except: invalid = True ++ assert invalid ++ ++ def open_with_wrong_password(wallet, filename, password): ++ invalid_password = False ++ try: wallet.open_wallet(filename, password = password) ++ except: invalid_password = True ++ assert invalid_password ++ ++ def restore_wallet(wallet, seed, filename = '', password = ''): ++ wallet.close_wallet() ++ if filename != '': ++ util_resources.remove_wallet_files(filename) ++ wallet.restore_deterministic_wallet(seed = seed, filename = filename, password = password) ++ wallet.auto_refresh(enable = False) ++ assert wallet.get_transfers() == {} ++ ++ def assert_correct_transfers(wallet, expected_transfers, expected_inc_transfers, expected_balance): ++ diff_transfers(wallet.get_transfers(), expected_transfers) ++ diff_incoming_transfers(wallet.incoming_transfers(transfer_type = 'all'), expected_inc_transfers) ++ assert wallet.get_balance().balance == expected_balance ++ ++ # Set up sender_wallet. Prepare to sweep single output to receiver. ++ # We're testing a sweep because it makes sure background sync can ++ # properly pick up txs which do not have a change output back to sender. ++ sender_wallet = self.wallet[0] ++ try: sender_wallet.close_wallet() ++ except: pass ++ sender_wallet.restore_deterministic_wallet(seed = seeds[0]) ++ sender_wallet.auto_refresh(enable = False) ++ sender_wallet.refresh() ++ res = sender_wallet.incoming_transfers(transfer_type = 'available') ++ unlocked = [x for x in res.transfers if x.unlocked and x.amount > 0] ++ assert len(unlocked) > 0 ++ ki = unlocked[0].key_image ++ amount = unlocked[0].amount ++ spent_txid = unlocked[0].tx_hash ++ sender_wallet.refresh() ++ res = sender_wallet.get_transfers() ++ out_len = 0 if 'out' not in res else len(res.out) ++ sender_starting_balance = sender_wallet.get_balance().balance ++ ++ # set up receiver_wallet ++ receiver_wallet = self.wallet[1] ++ try: receiver_wallet.close_wallet() ++ except: pass ++ receiver_wallet.restore_deterministic_wallet(seed = seeds[1]) ++ receiver_wallet.auto_refresh(enable = False) ++ receiver_wallet.refresh() ++ res = receiver_wallet.get_transfers() ++ in_len = 0 if 'in' not in res else len(res['in']) ++ receiver_starting_balance = receiver_wallet.get_balance().balance ++ ++ # transfer from sender_wallet to receiver_wallet ++ dst = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW' ++ res = sender_wallet.sweep_single(dst, key_image = ki) ++ assert len(res.tx_hash) == 32*2 ++ txid = res.tx_hash ++ assert res.fee > 0 ++ fee = res.fee ++ assert res.amount == amount - fee ++ ++ expected_sender_balance = sender_starting_balance - amount ++ expected_receiver_balance = receiver_starting_balance + (amount - fee) ++ ++ print('Checking background sync on outgoing wallet') ++ sender_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD) ++ sender_wallet.start_background_sync() ++ # Mine block to an uninvolved wallet ++ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) ++ # sender should still be able to scan the transfer normally because we ++ # spent an output that had a known key image ++ sender_wallet.refresh() ++ transfers = sender_wallet.get_transfers() ++ assert 'pending' not in transfers or len(transfers.pending) == 0 ++ assert 'pool' not in transfers or len (transfers.pool) == 0 ++ assert len(transfers.out) == out_len + 1 ++ tx = [x for x in transfers.out if x.txid == txid] ++ assert len(tx) == 1 ++ tx = tx[0] ++ assert tx.amount == amount - fee ++ assert tx.fee == fee ++ assert len(tx.destinations) == 1 ++ assert tx.destinations[0].amount == amount - fee ++ assert tx.destinations[0].address == dst ++ incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') ++ assert len([x for x in incoming_transfers.transfers if x.tx_hash == spent_txid and x.key_image == ki and x.spent]) == 1 ++ assert sender_wallet.get_balance().balance == expected_sender_balance ++ ++ # Restore and check background syncing outgoing wallet ++ restore_wallet(sender_wallet, seeds[0]) ++ sender_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD) ++ sender_wallet.start_background_sync() ++ sender_wallet.refresh() ++ for i, out_tx in enumerate(transfers.out): ++ if 'destinations' in out_tx: ++ del transfers.out[i]['destinations'] # destinations are not expected after wallet restore ++ # sender's balance should be higher because can't detect spends while ++ # background sync enabled, only receives ++ background_bal = sender_wallet.get_balance().balance ++ assert background_bal > expected_sender_balance ++ background_transfers = sender_wallet.get_transfers() ++ assert 'out' not in background_transfers or len(background_transfers.out) == 0 ++ assert 'in' in background_transfers and len(background_transfers['in']) > 0 ++ background_incoming_transfers = sender_wallet.incoming_transfers(transfer_type = 'all') ++ assert len(background_incoming_transfers) == len(incoming_transfers) ++ assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 ++ assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == spent_txid]) == 1 ++ ++ # Try to stop background sync with the wrong seed ++ stop_with_wrong_inputs(sender_wallet, wallet_password = '', seed = seeds[1]) ++ ++ # Stop background sync and check transfers update correctly ++ sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ # Check stopping a wallet with wallet files saved to disk ++ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]: ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password' ++ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) ++ sender_wallet.start_background_sync() ++ sender_wallet.refresh() ++ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) ++ stop_with_wrong_inputs(sender_wallet, 'wrong_password') ++ sender_wallet.stop_background_sync(wallet_password = 'test_password') ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ # Close wallet while background syncing, then reopen ++ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]: ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password' ++ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) ++ sender_wallet.start_background_sync() ++ sender_wallet.refresh() ++ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) ++ sender_wallet.close_wallet() ++ open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') ++ sender_wallet.open_wallet('test1', password = 'test_password') ++ # It should reopen with spend key loaded and correctly scan all transfers ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ # Close wallet while syncing normally, then reopen ++ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]: ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password' ++ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) ++ sender_wallet.refresh() ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ sender_wallet.close_wallet() ++ open_with_wrong_password(sender_wallet, 'test1', 'wrong_password') ++ sender_wallet.open_wallet('test1', password = 'test_password') ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ # Create background cache using custom password, then use it to sync, then reopen main wallet ++ for background_cache_password in ['background_password', '']: ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ sender_wallet.setup_background_sync(background_sync_type = CUSTOM_PASSWORD, wallet_password = 'test_password', background_cache_password = background_cache_password) ++ assert util_resources.file_exists('test1.background') ++ assert util_resources.file_exists('test1.background.keys') ++ sender_wallet.close_wallet() ++ open_with_wrong_password(sender_wallet, 'test1.background', 'test_password') ++ sender_wallet.open_wallet('test1.background', password = background_cache_password) ++ sender_wallet.refresh() ++ assert_correct_transfers(sender_wallet, background_transfers, background_incoming_transfers, background_bal) ++ sender_wallet.close_wallet() ++ sender_wallet.open_wallet('test1', password = 'test_password') ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ # Check that main wallet keeps background cache encrypted with custom password in sync ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = 'background_password') ++ sender_wallet.refresh() ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ sender_wallet.close_wallet() ++ sender_wallet.open_wallet('test1.background', password = 'background_password') ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ # Try using wallet password as custom background password ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ same_password = False ++ try: sender_wallet.setup_background_sync(background_sync_type = CUSTOM_PASSWORD, wallet_password = 'test_password', background_cache_password = 'test_password') ++ except: same_password = True ++ assert same_password ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ ++ # Turn off background sync ++ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]: ++ restore_wallet(sender_wallet, seeds[0], 'test1', 'test_password') ++ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password' ++ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = 'test_password', background_cache_password = background_cache_password) ++ if background_sync_type == CUSTOM_PASSWORD: ++ assert util_resources.file_exists('test1.background') ++ assert util_resources.file_exists('test1.background.keys') ++ sender_wallet.close_wallet() ++ assert util_resources.file_exists('test1.background') ++ assert util_resources.file_exists('test1.background.keys') ++ else: ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ sender_wallet.close_wallet() ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ sender_wallet.open_wallet('test1', password = 'test_password') ++ sender_wallet.setup_background_sync(background_sync_type = 'off', wallet_password = 'test_password') ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ sender_wallet.close_wallet() ++ assert not util_resources.file_exists('test1.background') ++ assert not util_resources.file_exists('test1.background.keys') ++ sender_wallet.open_wallet('test1', password = 'test_password') ++ ++ # Sanity check against outgoing wallet restored at height 0 ++ sender_wallet.close_wallet() ++ sender_wallet.restore_deterministic_wallet(seed = seeds[0], restore_height = 0) ++ sender_wallet.refresh() ++ assert_correct_transfers(sender_wallet, transfers, incoming_transfers, expected_sender_balance) ++ ++ print('Checking background sync on incoming wallet') ++ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD) ++ receiver_wallet.start_background_sync() ++ receiver_wallet.refresh() ++ transfers = receiver_wallet.get_transfers() ++ assert 'pending' not in transfers or len(transfers.pending) == 0 ++ assert 'pool' not in transfers or len (transfers.pool) == 0 ++ assert len(transfers['in']) == in_len + 1 ++ tx = [x for x in transfers['in'] if x.txid == txid] ++ assert len(tx) == 1 ++ tx = tx[0] ++ assert tx.amount == amount - fee ++ assert tx.fee == fee ++ incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') ++ assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image == '' and not x.spent]) == 1 ++ assert receiver_wallet.get_balance().balance == expected_receiver_balance ++ ++ # Restore and check background syncing incoming wallet ++ restore_wallet(receiver_wallet, seeds[1]) ++ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD) ++ receiver_wallet.start_background_sync() ++ receiver_wallet.refresh() ++ if 'out' in transfers: ++ for i, out_tx in enumerate(transfers.out): ++ if 'destinations' in out_tx: ++ del transfers.out[i]['destinations'] # destinations are not expected after wallet restore ++ background_bal = receiver_wallet.get_balance().balance ++ assert background_bal >= expected_receiver_balance ++ background_transfers = receiver_wallet.get_transfers() ++ assert 'out' not in background_transfers or len(background_transfers.out) == 0 ++ assert 'in' in background_transfers and len(background_transfers['in']) > 0 ++ background_incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') ++ assert len(background_incoming_transfers) == len(incoming_transfers) ++ assert len([x for x in background_incoming_transfers.transfers if x.spent or x.key_image != '']) == 0 ++ assert len([x for x in background_incoming_transfers.transfers if x.tx_hash == txid]) == 1 ++ ++ # Stop background sync and check transfers update correctly ++ receiver_wallet.stop_background_sync(wallet_password = '', seed = seeds[1]) ++ diff_transfers(receiver_wallet.get_transfers(), transfers) ++ incoming_transfers = receiver_wallet.incoming_transfers(transfer_type = 'all') ++ assert len(background_incoming_transfers) == len(incoming_transfers) ++ assert len([x for x in incoming_transfers.transfers if x.tx_hash == txid and x.key_image != '' and not x.spent]) == 1 ++ assert receiver_wallet.get_balance().balance == expected_receiver_balance ++ ++ # Check a fresh incoming wallet with wallet files saved to disk and encrypted with password ++ restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') ++ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD, wallet_password = 'test_password') ++ receiver_wallet.start_background_sync() ++ receiver_wallet.refresh() ++ assert_correct_transfers(receiver_wallet, background_transfers, background_incoming_transfers, background_bal) ++ stop_with_wrong_inputs(receiver_wallet, 'wrong_password') ++ receiver_wallet.stop_background_sync(wallet_password = 'test_password') ++ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) ++ ++ # Close receiver's wallet while background sync is enabled then reopen ++ restore_wallet(receiver_wallet, seeds[1], 'test2', 'test_password') ++ receiver_wallet.setup_background_sync(background_sync_type = REUSE_PASSWORD, wallet_password = 'test_password') ++ receiver_wallet.start_background_sync() ++ receiver_wallet.refresh() ++ diff_transfers(receiver_wallet.get_transfers(), background_transfers) ++ diff_incoming_transfers(receiver_wallet.incoming_transfers(transfer_type = 'all'), background_incoming_transfers) ++ assert receiver_wallet.get_balance().balance == background_bal ++ receiver_wallet.close_wallet() ++ receiver_wallet.open_wallet('test2', password = 'test_password') ++ # It should reopen with spend key loaded and correctly scan all transfers ++ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) ++ ++ # Sanity check against incoming wallet restored at height 0 ++ receiver_wallet.close_wallet() ++ receiver_wallet.restore_deterministic_wallet(seed = seeds[1], restore_height = 0) ++ receiver_wallet.refresh() ++ assert_correct_transfers(receiver_wallet, transfers, incoming_transfers, expected_receiver_balance) ++ ++ # Clean up ++ util_resources.remove_wallet_files('test1') ++ util_resources.remove_wallet_files('test2') ++ for i in range(2): ++ self.wallet[i].close_wallet() ++ self.wallet[i].restore_deterministic_wallet(seed = seeds[i]) ++ ++ def check_background_sync_reorg_recovery(self): ++ daemon = Daemon() ++ ++ print('Testing background sync reorg recovery') ++ ++ # Disconnect daemon from peers ++ daemon.out_peers(0) ++ ++ # Background sync type options ++ REUSE_PASSWORD = 'reuse-wallet-password' ++ CUSTOM_PASSWORD = 'custom-background-password' ++ ++ for background_sync_type in [REUSE_PASSWORD, CUSTOM_PASSWORD]: ++ # Set up wallet saved to disk ++ sender_wallet = self.wallet[0] ++ sender_wallet.close_wallet() ++ util_resources.remove_wallet_files('test1') ++ sender_wallet.restore_deterministic_wallet(seed = seeds[0], filename = 'test1', password = '') ++ sender_wallet.auto_refresh(enable = False) ++ sender_wallet.refresh() ++ sender_starting_balance = sender_wallet.get_balance().balance ++ ++ # Send tx and mine a block ++ amount = 1000000000000 ++ assert sender_starting_balance > amount ++ dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': amount} ++ res = sender_wallet.transfer([dst]) ++ assert len(res.tx_hash) == 32*2 ++ txid = res.tx_hash ++ ++ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) ++ ++ # Make sure the wallet can see the tx ++ sender_wallet.refresh() ++ transfers = sender_wallet.get_transfers() ++ assert 'pool' not in transfers or len (transfers.pool) == 0 ++ tx = [x for x in transfers.out if x.txid == txid] ++ assert len(tx) == 1 ++ tx = tx[0] ++ assert sender_wallet.get_balance().balance < (sender_starting_balance - amount) ++ ++ # Pop the block while background syncing ++ background_cache_password = None if background_sync_type == REUSE_PASSWORD else 'background_password' ++ sender_wallet.setup_background_sync(background_sync_type = background_sync_type, wallet_password = '', background_cache_password = background_cache_password) ++ sender_wallet.start_background_sync() ++ daemon.pop_blocks(1) ++ daemon.flush_txpool() ++ ++ daemon.generateblocks('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 1) ++ ++ # Make sure the wallet can no longer see the tx ++ sender_wallet.refresh() ++ sender_wallet.stop_background_sync(wallet_password = '', seed = seeds[0]) ++ transfers = sender_wallet.get_transfers() ++ no_tx = [x for x in transfers.out if x.txid == txid] ++ assert len(no_tx) == 0 ++ assert sender_wallet.get_balance().balance == sender_starting_balance ++ ++ # Clean up ++ daemon.out_peers(12) ++ util_resources.remove_wallet_files('test1') ++ self.wallet[0].close_wallet() ++ self.wallet[0].restore_deterministic_wallet(seed = seeds[0]) ++ ++ + def create(self): + print('Creating wallets') + self.wallet = [None] * len(seeds) +@@ -840,12 +1235,6 @@ class TransferTest(): + + print('Testing scan_tx') + +- def diff_transfers(actual_transfers, expected_transfers): +- diff = DeepDiff(actual_transfers, expected_transfers) +- if diff != {}: +- pp.pprint(diff) +- assert diff == {} +- + # set up sender_wallet + sender_wallet = self.wallet[0] + try: sender_wallet.close_wallet() +diff --git a/tests/functional_tests/util_resources.py b/tests/functional_tests/util_resources.py +index e030312da..3197187f4 100755 +--- a/tests/functional_tests/util_resources.py ++++ b/tests/functional_tests/util_resources.py +@@ -37,6 +37,8 @@ + from __future__ import print_function + import subprocess + import psutil ++import os ++import errno + + def available_ram_gb(): + ram_bytes = psutil.virtual_memory().available +@@ -51,3 +53,26 @@ def get_time_pi_seconds(cores, app_dir='.'): + miliseconds = int(decoded) + + return miliseconds / 1000.0 ++ ++def remove_file(name): ++ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] ++ assert WALLET_DIRECTORY != '' ++ try: ++ os.unlink(WALLET_DIRECTORY + '/' + name) ++ except OSError as e: ++ if e.errno != errno.ENOENT: ++ raise ++ ++def get_file_path(name): ++ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] ++ assert WALLET_DIRECTORY != '' ++ return WALLET_DIRECTORY + '/' + name ++ ++def remove_wallet_files(name): ++ for suffix in ['', '.keys', '.background', '.background.keys']: ++ remove_file(name + suffix) ++ ++def file_exists(name): ++ WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] ++ assert WALLET_DIRECTORY != '' ++ return os.path.isfile(WALLET_DIRECTORY + '/' + name) +diff --git a/tests/functional_tests/wallet.py b/tests/functional_tests/wallet.py +index 1ad05c98f..8182cecb2 100755 +--- a/tests/functional_tests/wallet.py ++++ b/tests/functional_tests/wallet.py +@@ -34,8 +34,7 @@ + + from __future__ import print_function + import sys +-import os +-import errno ++import util_resources + + from framework.wallet import Wallet + from framework.daemon import Daemon +@@ -54,24 +53,6 @@ class WalletTest(): + self.change_password() + self.store() + +- def remove_file(self, name): +- WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] +- assert WALLET_DIRECTORY != '' +- try: +- os.unlink(WALLET_DIRECTORY + '/' + name) +- except OSError as e: +- if e.errno != errno.ENOENT: +- raise +- +- def remove_wallet_files(self, name): +- for suffix in ['', '.keys']: +- self.remove_file(name + suffix) +- +- def file_exists(self, name): +- WALLET_DIRECTORY = os.environ['WALLET_DIRECTORY'] +- assert WALLET_DIRECTORY != '' +- return os.path.isfile(WALLET_DIRECTORY + '/' + name) +- + def reset(self): + print('Resetting blockchain') + daemon = Daemon() +@@ -333,7 +314,7 @@ class WalletTest(): + try: wallet.close_wallet() + except: pass + +- self.remove_wallet_files('test1') ++ util_resources.remove_wallet_files('test1') + + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + res = wallet.restore_deterministic_wallet(seed = seed, filename = 'test1') +@@ -359,7 +340,7 @@ class WalletTest(): + + wallet.close_wallet() + +- self.remove_wallet_files('test1') ++ util_resources.remove_wallet_files('test1') + + def store(self): + print('Testing store') +@@ -369,22 +350,26 @@ class WalletTest(): + try: wallet.close_wallet() + except: pass + +- self.remove_wallet_files('test1') ++ util_resources.remove_wallet_files('test1') + + seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted' + res = wallet.restore_deterministic_wallet(seed = seed, filename = 'test1') + assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm' + assert res.seed == seed + +- self.remove_file('test1') +- assert self.file_exists('test1.keys') +- assert not self.file_exists('test1') ++ util_resources.remove_file('test1') ++ assert util_resources.file_exists('test1.keys') ++ assert not util_resources.file_exists('test1') + wallet.store() +- assert self.file_exists('test1.keys') +- assert self.file_exists('test1') ++ assert util_resources.file_exists('test1.keys') ++ assert util_resources.file_exists('test1') + + wallet.close_wallet() +- self.remove_wallet_files('test1') ++ ++ wallet.open_wallet(filename = 'test1', password = '') ++ wallet.close_wallet() ++ ++ util_resources.remove_wallet_files('test1') + + + if __name__ == '__main__': +diff --git a/tests/unit_tests/wipeable_string.cpp b/tests/unit_tests/wipeable_string.cpp +index ef6964f9e..25121a02e 100644 +--- a/tests/unit_tests/wipeable_string.cpp ++++ b/tests/unit_tests/wipeable_string.cpp +@@ -211,3 +211,15 @@ TEST(wipeable_string, to_hex) + ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span<const uint8_t>((const uint8_t*)"", 0)) == epee::wipeable_string("")); + ASSERT_TRUE(epee::to_hex::wipeable_string(epee::span<const uint8_t>((const uint8_t*)"abc", 3)) == epee::wipeable_string("616263")); + } ++ ++TEST(wipeable_string, to_string) ++{ ++ // Converting a wipeable_string to a string defeats the purpose of wipeable_string, ++ // but nice to know this works ++ std::string str; ++ { ++ epee::wipeable_string wipeable_str("foo"); ++ str = std::string(wipeable_str.data(), wipeable_str.size()); ++ } ++ ASSERT_TRUE(str == std::string("foo")); ++} +diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py +index 1e10e1f86..ccf6ca93e 100644 +--- a/utils/python-rpc/framework/wallet.py ++++ b/utils/python-rpc/framework/wallet.py +@@ -1138,3 +1138,38 @@ class Wallet(object): + 'id': '0' + } + return self.rpc.send_json_rpc_request(frozen) ++ ++ def setup_background_sync(self, background_sync_type = 'off', wallet_password = '', background_cache_password = ''): ++ setup_background_sync = { ++ 'method': 'setup_background_sync', ++ 'jsonrpc': '2.0', ++ 'params' : { ++ 'background_sync_type': background_sync_type, ++ 'wallet_password': wallet_password, ++ 'background_cache_password': background_cache_password, ++ }, ++ 'id': '0' ++ } ++ return self.rpc.send_json_rpc_request(setup_background_sync) ++ ++ def start_background_sync(self): ++ start_background_sync = { ++ 'method': 'start_background_sync', ++ 'jsonrpc': '2.0', ++ 'params' : {}, ++ 'id': '0' ++ } ++ return self.rpc.send_json_rpc_request(start_background_sync) ++ ++ def stop_background_sync(self, wallet_password = '', seed = '', seed_offset = ''): ++ stop_background_sync = { ++ 'method': 'stop_background_sync', ++ 'jsonrpc': '2.0', ++ 'params' : { ++ 'wallet_password': wallet_password, ++ 'seed': seed, ++ 'seed_offset': seed_offset, ++ }, ++ 'id': '0' ++ } ++ return self.rpc.send_json_rpc_request(stop_background_sync) diff --git a/patches/0002-airgap.patch b/patches/0002-airgap.patch new file mode 100644 index 0000000..c3ce71b --- /dev/null +++ b/patches/0002-airgap.patch @@ -0,0 +1,184 @@ +From 559780d053549f01e9153bf43bad24c80255117d Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto <cyjan@mrcyjanek.net> +Date: Tue, 12 Mar 2024 10:09:50 +0100 +Subject: [PATCH] PATCH: airgap + +--- + src/wallet/api/wallet.cpp | 23 ++++++++++++++++++++++ + src/wallet/api/wallet.h | 2 ++ + src/wallet/api/wallet2_api.h | 3 +++ + src/wallet/wallet2.cpp | 38 ++++++++++++++++++++++++++++++------ + src/wallet/wallet2.h | 1 + + 5 files changed, 61 insertions(+), 6 deletions(-) + +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 1a9c6f674..42887dced 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -1129,6 +1129,24 @@ uint64_t WalletImpl::unlockedBalance(uint32_t accountIndex) const + return m_wallet->unlocked_balance(accountIndex, false); + } + ++uint64_t WalletImpl::viewOnlyBalance(uint32_t accountIndex, const std::vector<std::string> &key_images) const ++{ ++ clearStatus(); ++ ++ std::vector<crypto::key_image> kis; ++ for (const auto &key_image : key_images) { ++ crypto::key_image ki; ++ if (!epee::string_tools::hex_to_pod(key_image, ki)) ++ { ++ setStatusError(tr("failed to parse key image")); ++ return 0; ++ } ++ kis.push_back(ki); ++ } ++ ++ return m_wallet->view_only_balance(accountIndex, kis); ++} ++ + uint64_t WalletImpl::blockChainHeight() const + { + if(m_wallet->light_wallet()) { +@@ -1291,6 +1309,11 @@ bool WalletImpl::submitTransaction(const string &fileName) { + return true; + } + ++bool WalletImpl::hasUnknownKeyImages() const ++{ ++ return m_wallet->has_unknown_key_images(); ++} ++ + bool WalletImpl::exportKeyImages(const string &filename, bool all) + { + if (m_wallet->watch_only()) +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 9acd2871f..05d065c5c 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -122,6 +122,7 @@ public: + bool setProxy(const std::string &address) override; + uint64_t balance(uint32_t accountIndex = 0) const override; + uint64_t unlockedBalance(uint32_t accountIndex = 0) const override; ++ uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector<std::string> &key_images) const override; + uint64_t blockChainHeight() const override; + uint64_t approximateBlockChainHeight() const override; + uint64_t estimateBlockChainHeight() const override; +@@ -175,6 +176,7 @@ public: + virtual PendingTransaction * createSweepUnmixableTransaction() override; + bool submitTransaction(const std::string &fileName) override; + virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; ++ bool hasUnknownKeyImages() const override; + bool exportKeyImages(const std::string &filename, bool all = false) override; + bool importKeyImages(const std::string &filename) override; + bool exportOutputs(const std::string &filename, bool all = false) override; +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 4268b656e..4edaefefd 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -626,6 +626,7 @@ struct Wallet + result += unlockedBalance(i); + return result; + } ++ virtual uint64_t viewOnlyBalance(uint32_t accountIndex, const std::vector<std::string> &key_images = {}) const = 0; + + /** + * @brief watchOnly - checks if wallet is watch only +@@ -910,6 +911,8 @@ struct Wallet + virtual uint64_t estimateTransactionFee(const std::vector<std::pair<std::string, uint64_t>> &destinations, + PendingTransaction::Priority priority) const = 0; + ++ virtual bool hasUnknownKeyImages() const = 0; ++ + /*! + * \brief exportKeyImages - exports key images to file + * \param filename +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index 618c43cee..3477e084f 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -947,6 +947,16 @@ uint32_t get_subaddress_clamped_sum(uint32_t idx, uint32_t extra) + return idx + extra; + } + ++bool is_preferred_input(const std::vector<crypto::key_image>& preferred_input_list, const crypto::key_image& input) { ++ if (!preferred_input_list.empty()) { ++ auto it = std::find(preferred_input_list.begin(), preferred_input_list.end(), input); ++ if (it == preferred_input_list.end()) { ++ return false; ++ } ++ } ++ return true; ++} ++ + static void setup_shim(hw::wallet_shim * shim, tools::wallet2 * wallet) + { + shim->get_tx_pub_key_from_received_outs = std::bind(&tools::wallet2::get_tx_pub_key_from_received_outs, wallet, std::placeholders::_1); +@@ -7045,6 +7055,25 @@ uint64_t wallet2::unlocked_balance(uint32_t index_major, bool strict, uint64_t * + return amount; + } + //---------------------------------------------------------------------------------------------------- ++uint64_t wallet2::view_only_balance(uint32_t index_major, const std::vector<crypto::key_image>& selected_inputs) ++{ ++ uint64_t amount = 0; ++ for (const auto &td : m_transfers) { ++ if (is_preferred_input(selected_inputs, td.m_key_image) && ++ !is_spent(td, false) && ++ !td.m_frozen && ++ !td.m_key_image_partial && ++ td.m_key_image_known && ++ td.is_rct() && ++ is_transfer_unlocked(td) && ++ td.m_subaddr_index.major == index_major) ++ { ++ amount += td.m_amount; ++ } ++ } ++ return amount; ++} ++//---------------------------------------------------------------------------------------------------- + std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_major, bool strict) const + { + std::map<uint32_t, uint64_t> amount_per_subaddr; +@@ -7895,9 +7924,7 @@ bool wallet2::sign_tx(unsigned_tx_set &exported_txs, std::vector<wallet2::pendin + crypto::key_derivation derivation; + std::vector<crypto::key_derivation> additional_derivations; + +- // compute public keys from out secret keys +- crypto::public_key tx_pub_key; +- crypto::secret_key_to_public_key(txs[n].tx_key, tx_pub_key); ++ crypto::public_key tx_pub_key = get_tx_pub_key_from_extra(tx); + std::vector<crypto::public_key> additional_tx_pub_keys; + for (const crypto::secret_key &skey: txs[n].additional_tx_keys) + { +@@ -11295,7 +11322,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below fractional threshold " << print_money(fractional_threshold)); + continue; + } +- if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) ++ if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.m_key_image_known && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) + { +@@ -11345,8 +11372,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp + + LOG_PRINT_L2("Starting with " << num_nondust_outputs << " non-dust outputs and " << num_dust_outputs << " dust outputs"); + +- if (unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty()) +- return std::vector<wallet2::pending_tx>(); ++ THROW_WALLET_EXCEPTION_IF(unused_dust_indices_per_subaddr.empty() && unused_transfers_indices_per_subaddr.empty(), error::wallet_internal_error, "No enotes available to spend") + + // if empty, put dummy entry so that the front can be referenced later in the loop + if (unused_dust_indices_per_subaddr.empty()) +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index bfe3a4f19..406acac2b 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1172,6 +1172,7 @@ private: + // locked & unlocked balance of given or current subaddress account + uint64_t balance(uint32_t subaddr_index_major, bool strict) const; + uint64_t unlocked_balance(uint32_t subaddr_index_major, bool strict, uint64_t *blocks_to_unlock = NULL, uint64_t *time_to_unlock = NULL); ++ uint64_t view_only_balance(uint32_t index_major, const std::vector<crypto::key_image>& selected_inputs = {}); + // locked & unlocked balance per subaddress of given or current subaddress account + std::map<uint32_t, uint64_t> balance_per_subaddress(uint32_t subaddr_index_major, bool strict) const; + std::map<uint32_t, std::pair<uint64_t, std::pair<uint64_t, uint64_t>>> unlocked_balance_per_subaddress(uint32_t subaddr_index_major, bool strict); diff --git a/patches/0003-coin-control.patch b/patches/0003-coin-control.patch new file mode 100644 index 0000000..2d25beb --- /dev/null +++ b/patches/0003-coin-control.patch @@ -0,0 +1,916 @@ +From a95f52274d4a6a467e06bff60121850adba387ad Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto <cyjan@mrcyjanek.net> +Date: Tue, 12 Mar 2024 11:07:57 +0100 +Subject: [PATCH] PATCH: coin control + +--- + src/wallet/api/CMakeLists.txt | 8 +- + src/wallet/api/coins.cpp | 185 ++++++++++++++++++++++++++++++++++ + src/wallet/api/coins.h | 40 ++++++++ + src/wallet/api/coins_info.cpp | 122 ++++++++++++++++++++++ + src/wallet/api/coins_info.h | 71 +++++++++++++ + src/wallet/api/wallet.cpp | 31 +++++- + src/wallet/api/wallet.h | 10 +- + src/wallet/api/wallet2_api.h | 52 +++++++++- + src/wallet/wallet2.cpp | 46 ++++++++- + src/wallet/wallet2.h | 11 +- + 10 files changed, 558 insertions(+), 18 deletions(-) + create mode 100644 src/wallet/api/coins.cpp + create mode 100644 src/wallet/api/coins.h + create mode 100644 src/wallet/api/coins_info.cpp + create mode 100644 src/wallet/api/coins_info.h + +diff --git a/src/wallet/api/CMakeLists.txt b/src/wallet/api/CMakeLists.txt +index af7948d8a..bb740e2ac 100644 +--- a/src/wallet/api/CMakeLists.txt ++++ b/src/wallet/api/CMakeLists.txt +@@ -40,7 +40,9 @@ set(wallet_api_sources + address_book.cpp + subaddress.cpp + subaddress_account.cpp +- unsigned_transaction.cpp) ++ unsigned_transaction.cpp ++ coins.cpp ++ coins_info.cpp) + + set(wallet_api_headers + wallet2_api.h) +@@ -55,7 +57,9 @@ set(wallet_api_private_headers + address_book.h + subaddress.h + subaddress_account.h +- unsigned_transaction.h) ++ unsigned_transaction.h ++ coins.h ++ coins_info.h) + + monero_private_headers(wallet_api + ${wallet_api_private_headers}) +diff --git a/src/wallet/api/coins.cpp b/src/wallet/api/coins.cpp +new file mode 100644 +index 000000000..2321c638d +--- /dev/null ++++ b/src/wallet/api/coins.cpp +@@ -0,0 +1,185 @@ ++#include "coins.h" ++#include "coins_info.h" ++#include "wallet.h" ++#include "crypto/hash.h" ++#include "wallet/wallet2.h" ++#include "common_defines.h" ++ ++#include <string> ++#include <vector> ++ ++using namespace epee; ++ ++namespace Monero { ++ ++Coins::~Coins() = default; ++ ++CoinsImpl::CoinsImpl(WalletImpl *wallet) ++ : m_wallet(wallet) {} ++ ++CoinsImpl::~CoinsImpl() ++{ ++ for (auto t : m_rows) ++ delete t; ++} ++ ++int CoinsImpl::count() const ++{ ++ boost::shared_lock<boost::shared_mutex> lock(m_rowsMutex); ++ int result = m_rows.size(); ++ return result; ++} ++ ++CoinsInfo *CoinsImpl::coin(int index) const ++{ ++ boost::shared_lock<boost::shared_mutex> lock(m_rowsMutex); ++ // sanity check ++ if (index < 0) ++ return nullptr; ++ auto index_ = static_cast<unsigned>(index); ++ return index_ < m_rows.size() ? m_rows[index_] : nullptr; ++} ++ ++std::vector<CoinsInfo *> CoinsImpl::getAll() const ++{ ++ boost::shared_lock<boost::shared_mutex> lock(m_rowsMutex); ++ return m_rows; ++} ++ ++ ++void CoinsImpl::refresh() ++{ ++ LOG_PRINT_L2("Refreshing coins"); ++ ++ boost::unique_lock<boost::shared_mutex> lock(m_rowsMutex); ++ boost::shared_lock<boost::shared_mutex> transfers_lock(m_wallet->m_wallet->m_transfers_mutex); ++ ++ // delete old outputs; ++ for (auto t : m_rows) ++ delete t; ++ m_rows.clear(); ++ ++ for (size_t i = 0; i < m_wallet->m_wallet->get_num_transfer_details(); ++i) ++ { ++ const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(i); ++ ++ auto ci = new CoinsInfoImpl(); ++ ci->m_blockHeight = td.m_block_height; ++ ci->m_hash = string_tools::pod_to_hex(td.m_txid); ++ ci->m_internalOutputIndex = td.m_internal_output_index; ++ ci->m_globalOutputIndex = td.m_global_output_index; ++ ci->m_spent = td.m_spent; ++ ci->m_frozen = td.m_frozen; ++ ci->m_spentHeight = td.m_spent_height; ++ ci->m_amount = td.m_amount; ++ ci->m_rct = td.m_rct; ++ ci->m_keyImageKnown = td.m_key_image_known; ++ ci->m_pkIndex = td.m_pk_index; ++ ci->m_subaddrIndex = td.m_subaddr_index.minor; ++ ci->m_subaddrAccount = td.m_subaddr_index.major; ++ ci->m_address = m_wallet->m_wallet->get_subaddress_as_str(td.m_subaddr_index); // todo: this is expensive, cache maybe? ++ ci->m_addressLabel = m_wallet->m_wallet->get_subaddress_label(td.m_subaddr_index); ++ ci->m_keyImage = string_tools::pod_to_hex(td.m_key_image); ++ ci->m_unlockTime = td.m_tx.unlock_time; ++ ci->m_unlocked = m_wallet->m_wallet->is_transfer_unlocked(td); ++ ci->m_pubKey = string_tools::pod_to_hex(td.get_public_key()); ++ ci->m_coinbase = td.m_tx.vin.size() == 1 && td.m_tx.vin[0].type() == typeid(cryptonote::txin_gen); ++ ci->m_description = m_wallet->m_wallet->get_tx_note(td.m_txid); ++ ++ m_rows.push_back(ci); ++ } ++} ++ ++void CoinsImpl::setFrozen(std::string public_key) ++{ ++ crypto::public_key pk; ++ if (!epee::string_tools::hex_to_pod(public_key, pk)) ++ { ++ LOG_ERROR("Invalid public key: " << public_key); ++ return; ++ } ++ ++ try ++ { ++ m_wallet->m_wallet->freeze(pk); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("setFrozen: " << e.what()); ++ } ++} ++ ++void CoinsImpl::setFrozen(int index) ++{ ++ try ++ { ++ m_wallet->m_wallet->freeze(index); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("setLabel: " << e.what()); ++ } ++} ++ ++void CoinsImpl::thaw(std::string public_key) ++{ ++ crypto::public_key pk; ++ if (!epee::string_tools::hex_to_pod(public_key, pk)) ++ { ++ LOG_ERROR("Invalid public key: " << public_key); ++ return; ++ } ++ ++ try ++ { ++ m_wallet->m_wallet->thaw(pk); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("thaw: " << e.what()); ++ } ++} ++ ++void CoinsImpl::thaw(int index) ++{ ++ try ++ { ++ m_wallet->m_wallet->thaw(index); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("thaw: " << e.what()); ++ } ++} ++ ++bool CoinsImpl::isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) { ++ return m_wallet->m_wallet->is_transfer_unlocked(unlockTime, blockHeight); ++} ++ ++void CoinsImpl::setDescription(const std::string &public_key, const std::string &description) ++{ ++ crypto::public_key pk; ++ if (!epee::string_tools::hex_to_pod(public_key, pk)) ++ { ++ LOG_ERROR("Invalid public key: " << public_key); ++ return; ++ } ++ ++ try ++ { ++ const size_t index = m_wallet->m_wallet->get_transfer_details(pk); ++ const tools::wallet2::transfer_details &td = m_wallet->m_wallet->get_transfer_details(index); ++ m_wallet->m_wallet->set_tx_note(td.m_txid, description); ++ refresh(); ++ } ++ catch (const std::exception& e) ++ { ++ LOG_ERROR("setDescription: " << e.what()); ++ } ++} ++ ++} // namespace +diff --git a/src/wallet/api/coins.h b/src/wallet/api/coins.h +new file mode 100644 +index 000000000..b7a0a8642 +--- /dev/null ++++ b/src/wallet/api/coins.h +@@ -0,0 +1,40 @@ ++#ifndef FEATHER_COINS_H ++#define FEATHER_COINS_H ++ ++#include "wallet/api/wallet2_api.h" ++#include "wallet/wallet2.h" ++ ++namespace Monero { ++ ++class WalletImpl; ++ ++class CoinsImpl : public Coins ++{ ++public: ++ explicit CoinsImpl(WalletImpl * wallet); ++ ~CoinsImpl() override; ++ int count() const override; ++ CoinsInfo * coin(int index) const override; ++ std::vector<CoinsInfo*> getAll() const override; ++ void refresh() override; ++ ++ void setFrozen(std::string public_key) override; ++ void setFrozen(int index) override; ++ void thaw(std::string public_key) override; ++ void thaw(int index) override; ++ ++ bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) override; ++ ++ void setDescription(const std::string &public_key, const std::string &description) override; ++ ++private: ++ WalletImpl *m_wallet; ++ std::vector<CoinsInfo*> m_rows; ++ mutable boost::shared_mutex m_rowsMutex; ++}; ++ ++} ++ ++namespace Bitmonero = Monero; ++ ++#endif //FEATHER_COINS_H +diff --git a/src/wallet/api/coins_info.cpp b/src/wallet/api/coins_info.cpp +new file mode 100644 +index 000000000..5f2c4e1e4 +--- /dev/null ++++ b/src/wallet/api/coins_info.cpp +@@ -0,0 +1,122 @@ ++#include "coins_info.h" ++ ++using namespace std; ++ ++namespace Monero { ++ ++CoinsInfo::~CoinsInfo() = default; ++ ++CoinsInfoImpl::CoinsInfoImpl() ++ : m_blockHeight(0) ++ , m_internalOutputIndex(0) ++ , m_globalOutputIndex(0) ++ , m_spent(false) ++ , m_frozen(false) ++ , m_spentHeight(0) ++ , m_amount(0) ++ , m_rct(false) ++ , m_keyImageKnown(false) ++ , m_pkIndex(0) ++ , m_subaddrAccount(0) ++ , m_subaddrIndex(0) ++ , m_unlockTime(0) ++ , m_unlocked(false) ++{ ++ ++} ++ ++CoinsInfoImpl::~CoinsInfoImpl() = default; ++ ++uint64_t CoinsInfoImpl::blockHeight() const ++{ ++ return m_blockHeight; ++} ++ ++string CoinsInfoImpl::hash() const ++{ ++ return m_hash; ++} ++ ++size_t CoinsInfoImpl::internalOutputIndex() const { ++ return m_internalOutputIndex; ++} ++ ++uint64_t CoinsInfoImpl::globalOutputIndex() const ++{ ++ return m_globalOutputIndex; ++} ++ ++bool CoinsInfoImpl::spent() const ++{ ++ return m_spent; ++} ++ ++bool CoinsInfoImpl::frozen() const ++{ ++ return m_frozen; ++} ++ ++uint64_t CoinsInfoImpl::spentHeight() const ++{ ++ return m_spentHeight; ++} ++ ++uint64_t CoinsInfoImpl::amount() const ++{ ++ return m_amount; ++} ++ ++bool CoinsInfoImpl::rct() const { ++ return m_rct; ++} ++ ++bool CoinsInfoImpl::keyImageKnown() const { ++ return m_keyImageKnown; ++} ++ ++size_t CoinsInfoImpl::pkIndex() const { ++ return m_pkIndex; ++} ++ ++uint32_t CoinsInfoImpl::subaddrIndex() const { ++ return m_subaddrIndex; ++} ++ ++uint32_t CoinsInfoImpl::subaddrAccount() const { ++ return m_subaddrAccount; ++} ++ ++string CoinsInfoImpl::address() const { ++ return m_address; ++} ++ ++string CoinsInfoImpl::addressLabel() const { ++ return m_addressLabel; ++} ++ ++string CoinsInfoImpl::keyImage() const { ++ return m_keyImage; ++} ++ ++uint64_t CoinsInfoImpl::unlockTime() const { ++ return m_unlockTime; ++} ++ ++bool CoinsInfoImpl::unlocked() const { ++ return m_unlocked; ++} ++ ++string CoinsInfoImpl::pubKey() const { ++ return m_pubKey; ++} ++ ++bool CoinsInfoImpl::coinbase() const { ++ return m_coinbase; ++} ++ ++string CoinsInfoImpl::description() const { ++ return m_description; ++} ++} // namespace ++ ++namespace Bitmonero = Monero; +diff --git a/src/wallet/api/coins_info.h b/src/wallet/api/coins_info.h +new file mode 100644 +index 000000000..c43e45abd +--- /dev/null ++++ b/src/wallet/api/coins_info.h +@@ -0,0 +1,71 @@ ++#ifndef FEATHER_COINS_INFO_H ++#define FEATHER_COINS_INFO_H ++ ++#include "wallet/api/wallet2_api.h" ++#include <string> ++#include <ctime> ++ ++namespace Monero { ++ ++class CoinsImpl; ++ ++class CoinsInfoImpl : public CoinsInfo ++{ ++public: ++ CoinsInfoImpl(); ++ ~CoinsInfoImpl(); ++ ++ virtual uint64_t blockHeight() const override; ++ virtual std::string hash() const override; ++ virtual size_t internalOutputIndex() const override; ++ virtual uint64_t globalOutputIndex() const override; ++ virtual bool spent() const override; ++ virtual bool frozen() const override; ++ virtual uint64_t spentHeight() const override; ++ virtual uint64_t amount() const override; ++ virtual bool rct() const override; ++ virtual bool keyImageKnown() const override; ++ virtual size_t pkIndex() const override; ++ virtual uint32_t subaddrIndex() const override; ++ virtual uint32_t subaddrAccount() const override; ++ virtual std::string address() const override; ++ virtual std::string addressLabel() const override; ++ virtual std::string keyImage() const override; ++ virtual uint64_t unlockTime() const override; ++ virtual bool unlocked() const override; ++ virtual std::string pubKey() const override; ++ virtual bool coinbase() const override; ++ virtual std::string description() const override; ++ ++private: ++ uint64_t m_blockHeight; ++ std::string m_hash; ++ size_t m_internalOutputIndex; ++ uint64_t m_globalOutputIndex; ++ bool m_spent; ++ bool m_frozen; ++ uint64_t m_spentHeight; ++ uint64_t m_amount; ++ bool m_rct; ++ bool m_keyImageKnown; ++ size_t m_pkIndex; ++ uint32_t m_subaddrIndex; ++ uint32_t m_subaddrAccount; ++ std::string m_address; ++ std::string m_addressLabel; ++ std::string m_keyImage; ++ uint64_t m_unlockTime; ++ bool m_unlocked; ++ std::string m_pubKey; ++ bool m_coinbase; ++ std::string m_description; ++ ++ friend class CoinsImpl; ++ ++}; ++ ++} // namespace ++ ++namespace Bitmonero = Monero; ++ ++#endif //FEATHER_COINS_INFO_H +diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp +index 42887dced..2fe0d1d29 100644 +--- a/src/wallet/api/wallet.cpp ++++ b/src/wallet/api/wallet.cpp +@@ -35,6 +35,7 @@ + #include "transaction_history.h" + #include "address_book.h" + #include "subaddress.h" ++#include "coins.h" + #include "subaddress_account.h" + #include "common_defines.h" + #include "common/util.h" +@@ -469,6 +470,7 @@ WalletImpl::WalletImpl(NetworkType nettype, uint64_t kdf_rounds) + m_refreshEnabled = false; + m_addressBook.reset(new AddressBookImpl(this)); + m_subaddress.reset(new SubaddressImpl(this)); ++ m_coins.reset(new CoinsImpl(this)); + m_subaddressAccount.reset(new SubaddressAccountImpl(this)); + + +@@ -1752,7 +1754,7 @@ PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signDat + // - unconfirmed_transfer_details; + // - confirmed_transfer_details) + +-PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<string> &dst_addr, const string &payment_id, optional<std::vector<uint64_t>> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) ++PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<string> &dst_addr, const string &payment_id, optional<std::vector<uint64_t>> amount, uint32_t mixin_count, PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::set<std::string> &preferred_inputs) + + { + clearStatus(); +@@ -1821,6 +1823,19 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri + } + } + } ++ std::vector<crypto::key_image> preferred_input_list; ++ if (!preferred_inputs.empty()) { ++ for (const auto &public_key : preferred_inputs) { ++ crypto::key_image keyImage; ++ bool r = epee::string_tools::hex_to_pod(public_key, keyImage); ++ if (!r) { ++ error = true; ++ setStatusError(tr("failed to parse key image")); ++ break; ++ } ++ preferred_input_list.push_back(keyImage); ++ } ++ } + if (error) { + break; + } +@@ -1833,13 +1848,14 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri + fake_outs_count = m_wallet->adjust_mixin(mixin_count); + + if (amount) { ++ // (std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs, const std::vector<crypto::key_image>& preferred_input_list) + transaction->m_pending_tx = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, + adjusted_priority, +- extra, subaddr_account, subaddr_indices); ++ extra, subaddr_account, subaddr_indices, preferred_input_list); + } else { + transaction->m_pending_tx = m_wallet->create_transactions_all(0, info.address, info.is_subaddress, 1, fake_outs_count, 0 /* unlock_time */, + adjusted_priority, +- extra, subaddr_account, subaddr_indices); ++ extra, subaddr_account, subaddr_indices, preferred_input_list); + } + pendingTxPostProcess(transaction); + +@@ -1920,10 +1936,10 @@ PendingTransaction *WalletImpl::createTransactionMultDest(const std::vector<stri + } + + PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, +- PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) ++ PendingTransaction::Priority priority, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::set<std::string> &preferred_inputs) + + { +- return createTransactionMultDest(std::vector<string> {dst_addr}, payment_id, amount ? (std::vector<uint64_t> {*amount}) : (optional<std::vector<uint64_t>>()), mixin_count, priority, subaddr_account, subaddr_indices); ++ return createTransactionMultDest(std::vector<string> {dst_addr}, payment_id, amount ? (std::vector<uint64_t> {*amount}) : (optional<std::vector<uint64_t>>()), mixin_count, priority, subaddr_account, subaddr_indices, preferred_inputs); + } + + PendingTransaction *WalletImpl::createSweepUnmixableTransaction() +@@ -2048,6 +2064,11 @@ AddressBook *WalletImpl::addressBook() + return m_addressBook.get(); + } + ++Coins *WalletImpl::coins() ++{ ++ return m_coins.get(); ++} ++ + Subaddress *WalletImpl::subaddress() + { + return m_subaddress.get(); +diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h +index 05d065c5c..4a16ca028 100644 +--- a/src/wallet/api/wallet.h ++++ b/src/wallet/api/wallet.h +@@ -46,6 +46,7 @@ class PendingTransactionImpl; + class UnsignedTransactionImpl; + class AddressBookImpl; + class SubaddressImpl; ++class CoinsImpl; + class SubaddressAccountImpl; + struct Wallet2CallbackImpl; + +@@ -167,12 +168,14 @@ public: + optional<std::vector<uint64_t>> amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set<uint32_t> subaddr_indices = {}) override; ++ std::set<uint32_t> subaddr_indices = {}, ++ const std::set<std::string> &preferred_inputs = {}) override; + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, + optional<uint64_t> amount, uint32_t mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set<uint32_t> subaddr_indices = {}) override; ++ std::set<uint32_t> subaddr_indices = {}, ++ const std::set<std::string> &preferred_inputs = {}) override; + virtual PendingTransaction * createSweepUnmixableTransaction() override; + bool submitTransaction(const std::string &fileName) override; + virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) override; +@@ -195,6 +198,7 @@ public: + PendingTransaction::Priority priority) const override; + virtual TransactionHistory * history() override; + virtual AddressBook * addressBook() override; ++ virtual Coins * coins() override; + virtual Subaddress * subaddress() override; + virtual SubaddressAccount * subaddressAccount() override; + virtual void setListener(WalletListener * l) override; +@@ -266,6 +270,7 @@ private: + friend class TransactionHistoryImpl; + friend struct Wallet2CallbackImpl; + friend class AddressBookImpl; ++ friend class CoinsImpl; + friend class SubaddressImpl; + friend class SubaddressAccountImpl; + +@@ -282,6 +287,7 @@ private: + std::unique_ptr<Wallet2CallbackImpl> m_wallet2Callback; + std::unique_ptr<AddressBookImpl> m_addressBook; + std::unique_ptr<SubaddressImpl> m_subaddress; ++ std::unique_ptr<CoinsImpl> m_coins; + std::unique_ptr<SubaddressAccountImpl> m_subaddressAccount; + + // multi-threaded refresh stuff +diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h +index 4edaefefd..8a5c4135e 100644 +--- a/src/wallet/api/wallet2_api.h ++++ b/src/wallet/api/wallet2_api.h +@@ -261,6 +261,51 @@ struct AddressBook + virtual int lookupPaymentID(const std::string &payment_id) const = 0; + }; + ++/** ++ * @brief The CoinsInfo - interface for displaying coins information ++ */ ++struct CoinsInfo ++{ ++ virtual ~CoinsInfo() = 0; ++ ++ virtual uint64_t blockHeight() const = 0; ++ virtual std::string hash() const = 0; ++ virtual size_t internalOutputIndex() const = 0; ++ virtual uint64_t globalOutputIndex() const = 0; ++ virtual bool spent() const = 0; ++ virtual bool frozen() const = 0; ++ virtual uint64_t spentHeight() const = 0; ++ virtual uint64_t amount() const = 0; ++ virtual bool rct() const = 0; ++ virtual bool keyImageKnown() const = 0; ++ virtual size_t pkIndex() const = 0; ++ virtual uint32_t subaddrIndex() const = 0; ++ virtual uint32_t subaddrAccount() const = 0; ++ virtual std::string address() const = 0; ++ virtual std::string addressLabel() const = 0; ++ virtual std::string keyImage() const = 0; ++ virtual uint64_t unlockTime() const = 0; ++ virtual bool unlocked() const = 0; ++ virtual std::string pubKey() const = 0; ++ virtual bool coinbase() const = 0; ++ virtual std::string description() const = 0; ++}; ++ ++struct Coins ++{ ++ virtual ~Coins() = 0; ++ virtual int count() const = 0; ++ virtual CoinsInfo * coin(int index) const = 0; ++ virtual std::vector<CoinsInfo*> getAll() const = 0; ++ virtual void refresh() = 0; ++ virtual void setFrozen(std::string public_key) = 0; ++ virtual void setFrozen(int index) = 0; ++ virtual void thaw(std::string public_key) = 0; ++ virtual void thaw(int index) = 0; ++ virtual bool isTransferUnlocked(uint64_t unlockTime, uint64_t blockHeight) = 0; ++ virtual void setDescription(const std::string &public_key, const std::string &description) = 0; ++}; ++ + struct SubaddressRow { + public: + SubaddressRow(std::size_t _rowId, const std::string &_address, const std::string &_label): +@@ -854,7 +899,8 @@ struct Wallet + optional<std::vector<uint64_t>> amount, uint32_t mixin_count, + PendingTransaction::Priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set<uint32_t> subaddr_indices = {}) = 0; ++ std::set<uint32_t> subaddr_indices = {}, ++ const std::set<std::string> &preferred_inputs = {}) = 0; + + /*! + * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored +@@ -873,7 +919,8 @@ struct Wallet + optional<uint64_t> amount, uint32_t mixin_count, + PendingTransaction::Priority = PendingTransaction::Priority_Low, + uint32_t subaddr_account = 0, +- std::set<uint32_t> subaddr_indices = {}) = 0; ++ std::set<uint32_t> subaddr_indices = {}, ++ const std::set<std::string> &preferred_inputs = {}) = 0; + + /*! + * \brief createSweepUnmixableTransaction creates transaction with unmixable outputs. +@@ -987,6 +1034,7 @@ struct Wallet + + virtual TransactionHistory * history() = 0; + virtual AddressBook * addressBook() = 0; ++ virtual Coins * coins() = 0; + virtual Subaddress * subaddress() = 0; + virtual SubaddressAccount * subaddressAccount() = 0; + virtual void setListener(WalletListener *) = 0; +diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp +index 3477e084f..c3376f7e0 100644 +--- a/src/wallet/wallet2.cpp ++++ b/src/wallet/wallet2.cpp +@@ -2081,12 +2081,21 @@ bool wallet2::frozen(const multisig_tx_set& txs) const + + return false; + } ++void wallet2::freeze(const crypto::public_key &pk) ++{ ++ freeze(get_transfer_details(pk)); ++} + //---------------------------------------------------------------------------------------------------- + void wallet2::freeze(const crypto::key_image &ki) + { + freeze(get_transfer_details(ki)); + } + //---------------------------------------------------------------------------------------------------- ++void wallet2::thaw(const crypto::public_key &pk) ++{ ++ thaw(get_transfer_details(pk)); ++} ++//---------------------------------------------------------------------------------------------------- + void wallet2::thaw(const crypto::key_image &ki) + { + thaw(get_transfer_details(ki)); +@@ -2097,6 +2106,18 @@ bool wallet2::frozen(const crypto::key_image &ki) const + return frozen(get_transfer_details(ki)); + } + //---------------------------------------------------------------------------------------------------- ++size_t wallet2::get_transfer_details(const crypto::public_key &pk) const ++{ ++ for (size_t idx = 0; idx < m_transfers.size(); ++idx) ++ { ++ const transfer_details &td = m_transfers[idx]; ++ if (td.get_public_key() == pk) { ++ return idx; ++ } ++ } ++ CHECK_AND_ASSERT_THROW_MES(false, "Public key not found"); ++} ++//---------------------------------------------------------------------------------------------------- + size_t wallet2::get_transfer_details(const crypto::key_image &ki) const + { + for (size_t idx = 0; idx < m_transfers.size(); ++idx) +@@ -2500,6 +2521,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + uint64_t amount = tx.vout[o].amount ? tx.vout[o].amount : tx_scan_info[o].amount; + if (!pool) + { ++ boost::unique_lock<boost::shared_mutex> lock(m_transfers_mutex); + m_transfers.push_back(transfer_details{}); + transfer_details& td = m_transfers.back(); + td.m_block_height = height; +@@ -2603,6 +2625,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote + uint64_t extra_amount = amount - burnt; + if (!pool) + { ++ boost::unique_lock<boost::shared_mutex> lock(m_transfers_mutex); + transfer_details &td = m_transfers[kit->second]; + td.m_block_height = height; + td.m_internal_output_index = o; +@@ -10495,7 +10518,7 @@ void wallet2::transfer_selected_rct(std::vector<cryptonote::tx_destination_entry + LOG_PRINT_L2("transfer_selected_rct done"); + } + +-std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices) ++std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list) + { + std::vector<size_t> picks; + float current_output_relatdness = 1.0f; +@@ -10506,6 +10529,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (!is_spent(td, false) && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) +@@ -10526,6 +10552,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (!is_spent(td, false) && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1) + { + if (td.amount() > m_ignore_outputs_above || td.amount() < m_ignore_outputs_below) +@@ -10537,6 +10566,9 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui + for (size_t j = i + 1; j < m_transfers.size(); ++j) + { + const transfer_details& td2 = m_transfers[j]; ++ if (!is_preferred_input(preferred_input_list, td2.m_key_image)) { ++ continue; ++ } + if (td2.amount() > m_ignore_outputs_above || td2.amount() < m_ignore_outputs_below) + { + MDEBUG("Ignoring output " << j << " of amount " << print_money(td2.amount()) << " which is outside prescribed range [" << print_money(m_ignore_outputs_below) << ", " << print_money(m_ignore_outputs_above) << "]"); +@@ -11109,7 +11141,7 @@ bool wallet2::light_wallet_key_image_is_ours(const crypto::key_image& key_image, + // This system allows for sending (almost) the entire balance, since it does + // not generate spurious change in all txes, thus decreasing the instantaneous + // usable balance. +-std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs) ++std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list, const unique_index_container& subtract_fee_from_outputs) + { + //ensure device is let in NONE mode in any case + hw::device &hwdev = m_account.get_device(); +@@ -11317,6 +11349,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) + { + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below fractional threshold " << print_money(fractional_threshold)); +@@ -11401,7 +11436,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp + // will get us a known fee. + uint64_t estimated_fee = estimate_fee(use_per_byte_fee, use_rct, 2, fake_outs_count, 2, extra.size(), bulletproof, clsag, bulletproof_plus, use_view_tags, base_fee, fee_quantization_mask); + total_needed_money = needed_money + (subtract_fee_from_outputs.size() ? 0 : estimated_fee); +- preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices); ++ preferred_inputs = pick_preferred_rct_inputs(total_needed_money, subaddr_account, subaddr_indices, preferred_input_list); + if (!preferred_inputs.empty()) + { + string s; +@@ -11882,7 +11917,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, c + return true; + } + +-std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices) ++std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list) + { + std::vector<size_t> unused_transfers_indices; + std::vector<size_t> unused_dust_indices; +@@ -11911,6 +11946,9 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below + for (size_t i = 0; i < m_transfers.size(); ++i) + { + const transfer_details& td = m_transfers[i]; ++ if (!is_preferred_input(preferred_input_list, td.m_key_image)) { ++ continue; ++ } + if (m_ignore_fractional_outputs && td.amount() < fractional_threshold) + { + MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold)); +diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h +index 406acac2b..5eae908ad 100644 +--- a/src/wallet/wallet2.h ++++ b/src/wallet/wallet2.h +@@ -1207,8 +1207,8 @@ private: + bool parse_unsigned_tx_from_str(const std::string &unsigned_tx_st, unsigned_tx_set &exported_txs) const; + bool load_tx(const std::string &signed_filename, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set&)> accept_func = NULL); + bool parse_tx_from_str(const std::string &signed_tx_st, std::vector<tools::wallet2::pending_tx> &ptx, std::function<bool(const signed_tx_set &)> accept_func); +- std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose +- std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices); ++ std::vector<wallet2::pending_tx> create_transactions_2(std::vector<cryptonote::tx_destination_entry> dsts, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list = {}, const unique_index_container& subtract_fee_from_outputs = {}); // pass subaddr_indices by value on purpose ++ std::vector<wallet2::pending_tx> create_transactions_all(uint64_t below, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra, uint32_t subaddr_account, std::set<uint32_t> subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list = {}); + std::vector<wallet2::pending_tx> create_transactions_single(const crypto::key_image &ki, const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); + std::vector<wallet2::pending_tx> create_transactions_from(const cryptonote::account_public_address &address, bool is_subaddress, const size_t outputs, std::vector<size_t> unused_transfers_indices, std::vector<size_t> unused_dust_indices, const size_t fake_outs_count, const uint64_t unlock_time, uint32_t priority, const std::vector<uint8_t>& extra); + bool sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, const std::vector<cryptonote::tx_destination_entry>& dsts, const unique_index_container& subtract_fee_from_outputs = {}) const; +@@ -1559,6 +1559,7 @@ private: + uint64_t get_num_rct_outputs(); + size_t get_num_transfer_details() const { return m_transfers.size(); } + const transfer_details &get_transfer_details(size_t idx) const; ++ size_t get_transfer_details(const crypto::public_key &pk) const; + + uint8_t get_current_hard_fork(); + void get_hard_fork_info(uint8_t version, uint64_t &earliest_height); +@@ -1788,7 +1789,9 @@ private: + void freeze(size_t idx); + void thaw(size_t idx); + bool frozen(size_t idx) const; ++ void freeze(const crypto::public_key &pk); + void freeze(const crypto::key_image &ki); ++ void thaw(const crypto::public_key &pk); + void thaw(const crypto::key_image &ki); + bool frozen(const crypto::key_image &ki) const; + bool frozen(const transfer_details &td) const; +@@ -1829,6 +1832,8 @@ private: + + static std::string get_default_daemon_address() { CRITICAL_REGION_LOCAL(default_daemon_address_lock); return default_daemon_address; } + ++ boost::shared_mutex m_transfers_mutex; ++ + private: + /*! + * \brief Stores wallet information to wallet file. +@@ -1892,7 +1897,7 @@ private: + std::vector<uint64_t> get_unspent_amounts_vector(bool strict); + uint64_t get_dynamic_base_fee_estimate(); + float get_output_relatedness(const transfer_details &td0, const transfer_details &td1) const; +- std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices); ++ std::vector<size_t> pick_preferred_rct_inputs(uint64_t needed_money, uint32_t subaddr_account, const std::set<uint32_t> &subaddr_indices, const std::vector<crypto::key_image>& preferred_input_list); + void set_spent(size_t idx, uint64_t height); + void set_unspent(size_t idx); + bool is_spent(const transfer_details &td, bool strict = true) const; diff --git a/patches/0004-fix-build.patch b/patches/0004-fix-build.patch new file mode 100644 index 0000000..1a0daf6 --- /dev/null +++ b/patches/0004-fix-build.patch @@ -0,0 +1,134 @@ +From 1a05393366d94819c83b5e6983cb44938ac06c67 Mon Sep 17 00:00:00 2001 +From: Czarek Nakamoto <cyjan@mrcyjanek.net> +Date: Tue, 12 Mar 2024 17:59:13 +0100 +Subject: [PATCH] PATCH: fix build issues + +--- + contrib/depends/hosts/linux.mk | 8 +++---- + contrib/depends/packages/android_ndk.mk | 2 ++ + contrib/depends/packages/packages.mk | 2 +- + contrib/depends/packages/polyseed.mk | 23 +++++++++++++++++++ + contrib/depends/packages/sodium.mk | 2 +- + .../patches/polyseed/force-static-mingw.patch | 23 +++++++++++++++++++ + 6 files changed, 54 insertions(+), 6 deletions(-) + create mode 100644 contrib/depends/packages/polyseed.mk + create mode 100644 contrib/depends/patches/polyseed/force-static-mingw.patch + +diff --git a/contrib/depends/hosts/linux.mk b/contrib/depends/hosts/linux.mk +index 912fdb03c..b79799f30 100644 +--- a/contrib/depends/hosts/linux.mk ++++ b/contrib/depends/hosts/linux.mk +@@ -11,15 +11,15 @@ linux_debug_CXXFLAGS=$(linux_debug_CFLAGS) + linux_debug_CPPFLAGS=-D_GLIBCXX_DEBUG -D_GLIBCXX_DEBUG_PEDANTIC + + ifeq (86,$(findstring 86,$(build_arch))) +-i686_linux_CC=gcc -m32 +-i686_linux_CXX=g++ -m32 ++i686_linux_CC=i686-linux-gnu-gcc ++i686_linux_CXX=i686-linux-gnu-g++ + i686_linux_AR=ar + i686_linux_RANLIB=ranlib + i686_linux_NM=nm + i686_linux_STRIP=strip + +-x86_64_linux_CC=gcc -m64 +-x86_64_linux_CXX=g++ -m64 ++x86_64_linux_CC=x86_64-linux-gnu-gcc ++x86_64_linux_CXX=x86_64-linux-gnu-g++ + x86_64_linux_AR=ar + x86_64_linux_RANLIB=ranlib + x86_64_linux_NM=nm +diff --git a/contrib/depends/packages/android_ndk.mk b/contrib/depends/packages/android_ndk.mk +index 9b8a5332f..5deff76c7 100644 +--- a/contrib/depends/packages/android_ndk.mk ++++ b/contrib/depends/packages/android_ndk.mk +@@ -7,6 +7,8 @@ $(package)_sha256_hash=5dfbbdc2d3ba859fed90d0e978af87c71a91a5be1f6e1c40ba697503d + define $(package)_set_vars + $(package)_config_opts_arm=--arch arm + $(package)_config_opts_aarch64=--arch arm64 ++$(package)_config_opts_x86_64=--arch x86_64 ++$(package)_config_opts_i686=--arch x86 + endef + + define $(package)_extract_cmds +diff --git a/contrib/depends/packages/packages.mk b/contrib/depends/packages/packages.mk +index d2d1eca85..8783d4955 100644 +--- a/contrib/depends/packages/packages.mk ++++ b/contrib/depends/packages/packages.mk +@@ -1,4 +1,4 @@ +-packages:=boost openssl zeromq libiconv expat unbound ++packages:=boost openssl zeromq libiconv expat unbound polyseed + + # ccache is useless in gitian builds + ifneq ($(GITIAN),1) +diff --git a/contrib/depends/packages/polyseed.mk b/contrib/depends/packages/polyseed.mk +new file mode 100644 +index 000000000..47a532907 +--- /dev/null ++++ b/contrib/depends/packages/polyseed.mk +@@ -0,0 +1,23 @@ ++package=polyseed ++$(package)_version=2.0.0 ++$(package)_download_path=https://github.com/tevador/$(package)/archive/refs/tags/ ++$(package)_download_file=v$($(package)_version).tar.gz ++$(package)_file_name=$(package)-$($(package)_version).tar.gz ++$(package)_sha256_hash=f36282fcbcd68d32461b8230c89e1a40661bd46b91109681cec637433004135a ++$(package)_patches=force-static-mingw.patch ++ ++define $(package)_preprocess_cmds ++ patch -p1 < $($(package)_patch_dir)/force-static-mingw.patch ++endef ++ ++define $(package)_config_cmds ++ cmake -DCMAKE_INSTALL_PREFIX=$(host_prefix) -DCMAKE_C_COMPILER=${CC} . ++endef ++ ++define $(package)_build_cmds ++ $(MAKE) ++endef ++ ++define $(package)_stage_cmds ++ $(MAKE) DESTDIR=$($(package)_staging_dir) install ++endef +diff --git a/contrib/depends/packages/sodium.mk b/contrib/depends/packages/sodium.mk +index 87b34599e..68a5b48ba 100644 +--- a/contrib/depends/packages/sodium.mk ++++ b/contrib/depends/packages/sodium.mk +@@ -6,7 +6,7 @@ $(package)_sha256_hash=6f504490b342a4f8a4c4a02fc9b866cbef8622d5df4e5452b46be121e + $(package)_patches=disable-glibc-getrandom-getentropy.patch fix-whitespace.patch + + define $(package)_set_vars +-$(package)_config_opts=--enable-static --disable-shared ++$(package)_config_opts=--enable-static --disable-shared --with-pic + $(package)_config_opts+=--prefix=$(host_prefix) + endef + +diff --git a/contrib/depends/patches/polyseed/force-static-mingw.patch b/contrib/depends/patches/polyseed/force-static-mingw.patch +new file mode 100644 +index 000000000..f05cb2b6a +--- /dev/null ++++ b/contrib/depends/patches/polyseed/force-static-mingw.patch +@@ -0,0 +1,23 @@ ++--- a/include/polyseed.h +++++ b/include/polyseed.h ++@@ -93,13 +93,13 @@ Shared/static library definitions ++ - define POLYSEED_STATIC when linking to the static library ++ */ ++ #if defined(_WIN32) || defined(__CYGWIN__) ++- #ifdef POLYSEED_SHARED ++- #define POLYSEED_API __declspec(dllexport) ++- #elif !defined(POLYSEED_STATIC) ++- #define POLYSEED_API __declspec(dllimport) ++- #else ++- #define POLYSEED_API ++- #endif +++// #ifdef POLYSEED_SHARED +++// #define POLYSEED_API __declspec(dllexport) +++// #elif !defined(POLYSEED_STATIC) +++// #define POLYSEED_API __declspec(dllimport) +++// #else +++ #define POLYSEED_API +++// #endif ++ #define POLYSEED_PRIVATE ++ #else ++ #ifdef POLYSEED_SHARED |
