Merge pull request #4839
d71f89e2
device/trezor: device/trezor: correct device initialization, status check (Dusan Klinec)65b9bca7
device/trezor: python2 compatibility - bundle dependencies (Dusan Klinec)9cf636af
device/trezor: ask for KI sync on first refresh (Dusan Klinec)d21dad70
device: enable to use multiple independent device wallets (Dusan Klinec)318cc784
device/trezor: passphrase entry on host (Dusan Klinec)
This commit is contained in:
commit
0b31baf6e4
|
@ -56,7 +56,7 @@ if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON)
|
|||
endif()
|
||||
|
||||
if(USE_DEVICE_TREZOR_UDP_RELEASE)
|
||||
add_definitions(-DWITH_DEVICE_TREZOR_UDP_RELEASE=1)
|
||||
add_definitions(-DUSE_DEVICE_TREZOR_UDP_RELEASE=1)
|
||||
endif()
|
||||
|
||||
if (Protobuf_INCLUDE_DIR)
|
||||
|
|
|
@ -80,6 +80,14 @@ namespace hw {
|
|||
return false;
|
||||
}
|
||||
|
||||
class i_device_callback {
|
||||
public:
|
||||
virtual void on_button_request() {}
|
||||
virtual void on_pin_request(epee::wipeable_string & pin) {}
|
||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {}
|
||||
virtual ~i_device_callback() = default;
|
||||
};
|
||||
|
||||
class device {
|
||||
protected:
|
||||
std::string name;
|
||||
|
@ -129,6 +137,8 @@ namespace hw {
|
|||
virtual device_type get_type() const = 0;
|
||||
|
||||
virtual device_protocol_t device_protocol() const { return PROTOCOL_DEFAULT; };
|
||||
virtual void set_callback(i_device_callback * callback) {};
|
||||
virtual void set_derivation_path(const std::string &derivation_path) {};
|
||||
|
||||
/* ======================================================================= */
|
||||
/* LOCKER */
|
||||
|
|
|
@ -121,7 +121,8 @@ namespace trezor {
|
|||
const boost::optional<cryptonote::network_type> & network_type){
|
||||
AUTO_LOCK_CMD();
|
||||
require_connected();
|
||||
test_ping();
|
||||
device_state_reset_unsafe();
|
||||
require_initialized();
|
||||
|
||||
auto req = std::make_shared<messages::monero::MoneroGetAddress>();
|
||||
this->set_msg_addr<messages::monero::MoneroGetAddress>(req.get(), path, network_type);
|
||||
|
@ -136,7 +137,8 @@ namespace trezor {
|
|||
const boost::optional<cryptonote::network_type> & network_type){
|
||||
AUTO_LOCK_CMD();
|
||||
require_connected();
|
||||
test_ping();
|
||||
device_state_reset_unsafe();
|
||||
require_initialized();
|
||||
|
||||
auto req = std::make_shared<messages::monero::MoneroGetWatchKey>();
|
||||
this->set_msg_addr<messages::monero::MoneroGetWatchKey>(req.get(), path, network_type);
|
||||
|
@ -152,7 +154,8 @@ namespace trezor {
|
|||
{
|
||||
AUTO_LOCK_CMD();
|
||||
require_connected();
|
||||
test_ping();
|
||||
device_state_reset_unsafe();
|
||||
require_initialized();
|
||||
|
||||
std::shared_ptr<messages::monero::MoneroKeyImageExportInitRequest> req;
|
||||
|
||||
|
@ -238,12 +241,11 @@ namespace trezor {
|
|||
cpend.construction_data = cdata.tx_data;
|
||||
|
||||
// Transaction check
|
||||
cryptonote::blobdata tx_blob;
|
||||
cryptonote::transaction tx_deserialized;
|
||||
bool r = cryptonote::t_serializable_object_to_blob(cpend.tx, tx_blob);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed");
|
||||
r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed");
|
||||
try {
|
||||
transaction_check(cdata, aux_data);
|
||||
} catch(const std::exception &e){
|
||||
throw exc::ProtocolException(std::string("Transaction verification failed: ") + e.what());
|
||||
}
|
||||
|
||||
std::string key_images;
|
||||
bool all_are_txin_to_key = std::all_of(cdata.tx.vin.begin(), cdata.tx.vin.end(), [&](const cryptonote::txin_v& s_e) -> bool
|
||||
|
@ -283,7 +285,8 @@ namespace trezor {
|
|||
{
|
||||
AUTO_LOCK_CMD();
|
||||
require_connected();
|
||||
test_ping();
|
||||
device_state_reset_unsafe();
|
||||
require_initialized();
|
||||
|
||||
CHECK_AND_ASSERT_THROW_MES(idx < unsigned_tx.txes.size(), "Invalid transaction index");
|
||||
signer = std::make_shared<protocol::tx::Signer>(wallet, &unsigned_tx, idx, &aux_data);
|
||||
|
@ -294,6 +297,7 @@ namespace trezor {
|
|||
// Step: Init
|
||||
auto init_msg = signer->step_init();
|
||||
this->set_msg_addr(init_msg.get());
|
||||
transaction_pre_check(init_msg);
|
||||
|
||||
auto response = this->client_exchange<messages::monero::MoneroTransactionInitAck>(init_msg);
|
||||
signer->step_init_ack(response);
|
||||
|
@ -351,6 +355,59 @@ namespace trezor {
|
|||
signer->step_final_ack(ack_final);
|
||||
}
|
||||
|
||||
void device_trezor::transaction_pre_check(std::shared_ptr<messages::monero::MoneroTransactionInitRequest> init_msg)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(init_msg, "TransactionInitRequest is empty");
|
||||
CHECK_AND_ASSERT_THROW_MES(init_msg->has_tsx_data(), "TransactionInitRequest has no transaction data");
|
||||
CHECK_AND_ASSERT_THROW_MES(m_features, "Device state not initialized"); // make sure the caller did not reset features
|
||||
const bool nonce_required = init_msg->tsx_data().has_payment_id() && init_msg->tsx_data().payment_id().size() > 0;
|
||||
|
||||
if (nonce_required){
|
||||
// Versions 2.0.9 and lower do not support payment ID
|
||||
CHECK_AND_ASSERT_THROW_MES(m_features->has_major_version() && m_features->has_minor_version() && m_features->has_patch_version(), "Invalid Trezor firmware version information");
|
||||
const uint32_t vma = m_features->major_version();
|
||||
const uint32_t vmi = m_features->minor_version();
|
||||
const uint32_t vpa = m_features->patch_version();
|
||||
if (vma < 2 || (vma == 2 && vmi == 0 && vpa <= 9)) {
|
||||
throw exc::TrezorException("Trezor firmware 2.0.9 and lower does not support transactions with short payment IDs or integrated addresses. Please update.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void device_trezor::transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data)
|
||||
{
|
||||
// Simple serialization check
|
||||
cryptonote::blobdata tx_blob;
|
||||
cryptonote::transaction tx_deserialized;
|
||||
bool r = cryptonote::t_serializable_object_to_blob(tdata.tx, tx_blob);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Transaction serialization failed");
|
||||
r = cryptonote::parse_and_validate_tx_from_blob(tx_blob, tx_deserialized);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "Transaction deserialization failed");
|
||||
|
||||
// Extras check
|
||||
std::vector<cryptonote::tx_extra_field> tx_extra_fields;
|
||||
cryptonote::tx_extra_nonce nonce;
|
||||
|
||||
r = cryptonote::parse_tx_extra(tdata.tx.extra, tx_extra_fields);
|
||||
CHECK_AND_ASSERT_THROW_MES(r, "tx.extra parsing failed");
|
||||
|
||||
const bool nonce_required = tdata.tsx_data.has_payment_id() && tdata.tsx_data.payment_id().size() > 0;
|
||||
const bool has_nonce = cryptonote::find_tx_extra_field_by_type(tx_extra_fields, nonce);
|
||||
CHECK_AND_ASSERT_THROW_MES(has_nonce == nonce_required, "Transaction nonce presence inconsistent");
|
||||
|
||||
if (nonce_required){
|
||||
const std::string & payment_id = tdata.tsx_data.payment_id();
|
||||
if (payment_id.size() == 32){
|
||||
crypto::hash payment_id_long{};
|
||||
CHECK_AND_ASSERT_THROW_MES(cryptonote::get_payment_id_from_tx_extra_nonce(nonce.nonce, payment_id_long), "Long payment ID not present");
|
||||
|
||||
} else if (payment_id.size() == 8){
|
||||
crypto::hash8 payment_id_short{};
|
||||
CHECK_AND_ASSERT_THROW_MES(cryptonote::get_encrypted_payment_id_from_tx_extra_nonce(nonce.nonce, payment_id_short), "Short payment ID not present");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#else //WITH_DEVICE_TREZOR
|
||||
|
||||
void register_all(std::map<std::string, std::unique_ptr<device>> ®istry) {
|
||||
|
|
|
@ -57,9 +57,8 @@ namespace trezor {
|
|||
*/
|
||||
class device_trezor : public hw::trezor::device_trezor_base, public hw::device_cold {
|
||||
protected:
|
||||
// To speed up blockchain parsing the view key maybe handle here.
|
||||
crypto::secret_key viewkey;
|
||||
bool has_view_key;
|
||||
void transaction_pre_check(std::shared_ptr<messages::monero::MoneroTransactionInitRequest> init_msg);
|
||||
void transaction_check(const protocol::tx::TData & tdata, const hw::tx_aux_data & aux_data);
|
||||
|
||||
public:
|
||||
device_trezor();
|
||||
|
|
|
@ -28,6 +28,9 @@
|
|||
//
|
||||
|
||||
#include "device_trezor_base.hpp"
|
||||
#include <boost/algorithm/string/classification.hpp>
|
||||
#include <boost/algorithm/string/split.hpp>
|
||||
#include <boost/regex.hpp>
|
||||
|
||||
namespace hw {
|
||||
namespace trezor {
|
||||
|
@ -36,10 +39,11 @@ namespace trezor {
|
|||
|
||||
#undef MONERO_DEFAULT_LOG_CATEGORY
|
||||
#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor"
|
||||
#define TREZOR_BIP44_HARDENED_ZERO 0x80000000
|
||||
|
||||
const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080, 0x80000000};
|
||||
const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080};
|
||||
|
||||
device_trezor_base::device_trezor_base() {
|
||||
device_trezor_base::device_trezor_base(): m_callback(nullptr) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -61,7 +65,7 @@ namespace trezor {
|
|||
}
|
||||
|
||||
bool device_trezor_base::set_name(const std::string & name) {
|
||||
this->full_name = name;
|
||||
this->m_full_name = name;
|
||||
this->name = "";
|
||||
|
||||
auto delim = name.find(':');
|
||||
|
@ -73,10 +77,10 @@ namespace trezor {
|
|||
}
|
||||
|
||||
const std::string device_trezor_base::get_name() const {
|
||||
if (this->full_name.empty()) {
|
||||
if (this->m_full_name.empty()) {
|
||||
return std::string("<disconnected:").append(this->name).append(">");
|
||||
}
|
||||
return this->full_name;
|
||||
return this->m_full_name;
|
||||
}
|
||||
|
||||
bool device_trezor_base::init() {
|
||||
|
@ -135,6 +139,9 @@ namespace trezor {
|
|||
}
|
||||
|
||||
bool device_trezor_base::disconnect() {
|
||||
m_device_state.clear();
|
||||
m_features.reset();
|
||||
|
||||
if (m_transport){
|
||||
try {
|
||||
m_transport->close();
|
||||
|
@ -189,6 +196,25 @@ namespace trezor {
|
|||
}
|
||||
}
|
||||
|
||||
void device_trezor_base::require_initialized(){
|
||||
if (!m_features){
|
||||
throw exc::TrezorException("Device state not initialized");
|
||||
}
|
||||
|
||||
if (m_features->has_bootloader_mode() && m_features->bootloader_mode()){
|
||||
throw exc::TrezorException("Device is in the bootloader mode");
|
||||
}
|
||||
|
||||
if (m_features->has_firmware_present() && !m_features->firmware_present()){
|
||||
throw exc::TrezorException("Device has no firmware loaded");
|
||||
}
|
||||
|
||||
// Hard requirement on initialized field, has to be there.
|
||||
if (!m_features->has_initialized() || !m_features->initialized()){
|
||||
throw exc::TrezorException("Device is not initialized");
|
||||
}
|
||||
}
|
||||
|
||||
void device_trezor_base::call_ping_unsafe(){
|
||||
auto pingMsg = std::make_shared<messages::management::Ping>();
|
||||
pingMsg->set_message("PING");
|
||||
|
@ -213,7 +239,7 @@ namespace trezor {
|
|||
void device_trezor_base::write_raw(const google::protobuf::Message * msg){
|
||||
require_connected();
|
||||
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
||||
this->getTransport()->write(*msg);
|
||||
this->get_transport()->write(*msg);
|
||||
}
|
||||
|
||||
GenericMessage device_trezor_base::read_raw(){
|
||||
|
@ -221,7 +247,7 @@ namespace trezor {
|
|||
std::shared_ptr<google::protobuf::Message> msg_resp;
|
||||
hw::trezor::messages::MessageType msg_resp_type;
|
||||
|
||||
this->getTransport()->read(msg_resp, &msg_resp_type);
|
||||
this->get_transport()->read(msg_resp, &msg_resp_type);
|
||||
return GenericMessage(msg_resp_type, msg_resp);
|
||||
}
|
||||
|
||||
|
@ -252,6 +278,39 @@ namespace trezor {
|
|||
}
|
||||
}
|
||||
|
||||
void device_trezor_base::ensure_derivation_path() noexcept {
|
||||
if (m_wallet_deriv_path.empty()){
|
||||
m_wallet_deriv_path.push_back(TREZOR_BIP44_HARDENED_ZERO); // default 0'
|
||||
}
|
||||
}
|
||||
|
||||
void device_trezor_base::set_derivation_path(const std::string &deriv_path){
|
||||
this->m_wallet_deriv_path.clear();
|
||||
|
||||
if (deriv_path.empty() || deriv_path == "-"){
|
||||
ensure_derivation_path();
|
||||
return;
|
||||
}
|
||||
|
||||
CHECK_AND_ASSERT_THROW_MES(deriv_path.size() <= 255, "Derivation path is too long");
|
||||
|
||||
std::vector<std::string> fields;
|
||||
boost::split(fields, deriv_path, boost::is_any_of("/"));
|
||||
CHECK_AND_ASSERT_THROW_MES(fields.size() <= 10, "Derivation path is too long");
|
||||
|
||||
boost::regex rgx("^([0-9]+)'?$");
|
||||
boost::cmatch match;
|
||||
|
||||
this->m_wallet_deriv_path.reserve(fields.size());
|
||||
for(const std::string & cur : fields){
|
||||
const bool ok = boost::regex_match(cur.c_str(), match, rgx);
|
||||
CHECK_AND_ASSERT_THROW_MES(ok, "Invalid wallet code: " << deriv_path << ". Invalid path element: " << cur);
|
||||
CHECK_AND_ASSERT_THROW_MES(match[0].length() > 0, "Invalid wallet code: " << deriv_path << ". Invalid path element: " << cur);
|
||||
|
||||
const unsigned long cidx = std::stoul(match[0].str()) | TREZOR_BIP44_HARDENED_ZERO;
|
||||
this->m_wallet_deriv_path.push_back((unsigned int)cidx);
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================================================================= */
|
||||
/* TREZOR PROTOCOL */
|
||||
|
@ -277,6 +336,25 @@ namespace trezor {
|
|||
return false;
|
||||
}
|
||||
|
||||
void device_trezor_base::device_state_reset_unsafe()
|
||||
{
|
||||
require_connected();
|
||||
auto initMsg = std::make_shared<messages::management::Initialize>();
|
||||
|
||||
if(!m_device_state.empty()) {
|
||||
initMsg->set_allocated_state(&m_device_state);
|
||||
}
|
||||
|
||||
m_features = this->client_exchange<messages::management::Features>(initMsg);
|
||||
initMsg->release_state();
|
||||
}
|
||||
|
||||
void device_trezor_base::device_state_reset()
|
||||
{
|
||||
AUTO_LOCK_CMD();
|
||||
device_state_reset_unsafe();
|
||||
}
|
||||
|
||||
void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg)
|
||||
{
|
||||
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
||||
|
@ -324,7 +402,13 @@ namespace trezor {
|
|||
// TODO: remove passphrase from memory
|
||||
m.set_passphrase(passphrase.data(), passphrase.size());
|
||||
}
|
||||
|
||||
if (!m_device_state.empty()){
|
||||
m.set_allocated_state(&m_device_state);
|
||||
}
|
||||
|
||||
resp = call_raw(&m);
|
||||
m.release_state();
|
||||
}
|
||||
|
||||
void device_trezor_base::on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg)
|
||||
|
@ -332,10 +416,7 @@ namespace trezor {
|
|||
MDEBUG("on_passhprase_state_request");
|
||||
CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
|
||||
|
||||
if (m_callback){
|
||||
m_callback->on_passphrase_state_request(msg->state());
|
||||
}
|
||||
|
||||
m_device_state = msg->state();
|
||||
messages::common::PassphraseStateAck m;
|
||||
resp = call_raw(&m);
|
||||
}
|
||||
|
|
|
@ -57,17 +57,6 @@ namespace trezor {
|
|||
#ifdef WITH_DEVICE_TREZOR
|
||||
class device_trezor_base;
|
||||
|
||||
/**
|
||||
* Trezor device callbacks
|
||||
*/
|
||||
class trezor_callback {
|
||||
public:
|
||||
virtual void on_button_request() {};
|
||||
virtual void on_pin_request(epee::wipeable_string & pin) {};
|
||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {};
|
||||
virtual void on_passphrase_state_request(const std::string & state) {};
|
||||
};
|
||||
|
||||
/**
|
||||
* TREZOR device template with basic functions
|
||||
*/
|
||||
|
@ -79,9 +68,12 @@ namespace trezor {
|
|||
mutable boost::mutex command_locker;
|
||||
|
||||
std::shared_ptr<Transport> m_transport;
|
||||
std::shared_ptr<trezor_callback> m_callback;
|
||||
i_device_callback * m_callback;
|
||||
|
||||
std::string full_name;
|
||||
std::string m_full_name;
|
||||
std::vector<unsigned int> m_wallet_deriv_path;
|
||||
std::string m_device_state; // returned after passphrase entry, session
|
||||
std::shared_ptr<messages::management::Features> m_features; // features from the last device reset
|
||||
|
||||
cryptonote::network_type network_type;
|
||||
|
||||
|
@ -90,8 +82,11 @@ namespace trezor {
|
|||
//
|
||||
|
||||
void require_connected();
|
||||
void require_initialized();
|
||||
void call_ping_unsafe();
|
||||
void test_ping();
|
||||
void device_state_reset_unsafe();
|
||||
void ensure_derivation_path() noexcept;
|
||||
|
||||
// Communication methods
|
||||
|
||||
|
@ -139,7 +134,7 @@ namespace trezor {
|
|||
// Scoped session closer
|
||||
BOOST_SCOPE_EXIT_ALL(&, this) {
|
||||
if (open_session){
|
||||
this->getTransport()->close();
|
||||
this->get_transport()->close();
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -187,9 +182,13 @@ namespace trezor {
|
|||
msg->add_address_n(x);
|
||||
}
|
||||
} else {
|
||||
ensure_derivation_path();
|
||||
for (unsigned int i : DEFAULT_BIP44_PATH) {
|
||||
msg->add_address_n(i);
|
||||
}
|
||||
for (unsigned int i : m_wallet_deriv_path) {
|
||||
msg->add_address_n(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (network_type){
|
||||
|
@ -212,16 +211,26 @@ namespace trezor {
|
|||
bool reset();
|
||||
|
||||
// Default derivation path for Monero
|
||||
static const uint32_t DEFAULT_BIP44_PATH[3];
|
||||
static const uint32_t DEFAULT_BIP44_PATH[2];
|
||||
|
||||
std::shared_ptr<Transport> getTransport(){
|
||||
std::shared_ptr<Transport> get_transport(){
|
||||
return m_transport;
|
||||
}
|
||||
|
||||
std::shared_ptr<trezor_callback> getCallback(){
|
||||
void set_callback(i_device_callback * callback) override {
|
||||
m_callback = callback;
|
||||
}
|
||||
|
||||
i_device_callback * get_callback(){
|
||||
return m_callback;
|
||||
}
|
||||
|
||||
std::shared_ptr<messages::management::Features> & get_features() {
|
||||
return m_features;
|
||||
}
|
||||
|
||||
void set_derivation_path(const std::string &deriv_path) override;
|
||||
|
||||
/* ======================================================================= */
|
||||
/* SETUP/TEARDOWN */
|
||||
/* ======================================================================= */
|
||||
|
@ -249,6 +258,11 @@ namespace trezor {
|
|||
*/
|
||||
bool ping();
|
||||
|
||||
/**
|
||||
* Performs Initialize call to the Trezor, resets to known state.
|
||||
*/
|
||||
void device_state_reset();
|
||||
|
||||
// Protocol callbacks
|
||||
void on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg);
|
||||
void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg);
|
||||
|
|
|
@ -10,14 +10,28 @@ Install `protoc` for your distribution. Requirements:
|
|||
|
||||
|
||||
Soft requirement: Python 3, can be easily installed with [pyenv].
|
||||
If Python 3 is used there are no additional python dependencies.
|
||||
|
||||
### Python 2
|
||||
Since Cmake 3.12 the `FindPython` module is used to locate the Python
|
||||
interpreter in your system. It preferably searches for Python 3, if none
|
||||
is found, it searches for Python 2.
|
||||
|
||||
Workaround if there is no Python3 available:
|
||||
Lower version of the cmake uses another module which does not guarantee
|
||||
ordering. If you want to override the selected python you can do it in
|
||||
the following way:
|
||||
|
||||
```bash
|
||||
pip install backports.tempfile
|
||||
```
|
||||
export TREZOR_PYTHON=`which python3`
|
||||
```
|
||||
|
||||
|
||||
### Python 2.7+
|
||||
|
||||
Python 3 has `tempfile.TemporaryDirectory` available but Python 2 lacks
|
||||
this class so the message generation code uses `backports.tempfile` package
|
||||
bundled in the repository.
|
||||
|
||||
The minimal Python versions are 2.7 and 3.4
|
||||
|
||||
### Regenerate messages
|
||||
|
||||
|
|
|
@ -14,12 +14,18 @@ import hashlib
|
|||
try:
|
||||
from tempfile import TemporaryDirectory
|
||||
except:
|
||||
# Py2 backward compatibility, optionally installed by user
|
||||
# pip install backports.tempfile
|
||||
# Py2 backward compatibility, using bundled sources.
|
||||
# Original source: pip install backports.tempfile
|
||||
try:
|
||||
from backports.tempfile import TemporaryDirectory
|
||||
# Try bundled python version
|
||||
import sys
|
||||
sys.path.append(os.path.dirname(__file__))
|
||||
from py2backports.tempfile import TemporaryDirectory
|
||||
|
||||
except:
|
||||
raise EnvironmentError('TemporaryDirectory could not be imported. Try: pip install backports.tempfile')
|
||||
raise EnvironmentError('Python 2.7+ or 3.4+ is required. '
|
||||
'TemporaryDirectory is not available in Python 2.'
|
||||
'Try to specify python to use, e.g.: "export TREZOR_PYTHON=`which python3`"')
|
||||
|
||||
|
||||
AUTO_HEADER = "# Automatically generated by pb2cpp\n"
|
||||
|
|
|
@ -0,0 +1,72 @@
|
|||
"""
|
||||
https://github.com/pjdelport/backports.tempfile/blob/master/src/backports/tempfile.py
|
||||
Partial backport of Python 3.5's tempfile module:
|
||||
TemporaryDirectory
|
||||
Backport modifications are marked with marked with "XXX backport".
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import sys
|
||||
import warnings as _warnings
|
||||
from shutil import rmtree as _rmtree
|
||||
|
||||
from py2backports.weakref import finalize
|
||||
|
||||
|
||||
# XXX backport: Rather than backporting all of mkdtemp(), we just create a
|
||||
# thin wrapper implementing its Python 3.5 signature.
|
||||
if sys.version_info < (3, 5):
|
||||
from tempfile import mkdtemp as old_mkdtemp
|
||||
|
||||
def mkdtemp(suffix=None, prefix=None, dir=None):
|
||||
"""
|
||||
Wrap `tempfile.mkdtemp()` to make the suffix and prefix optional (like Python 3.5).
|
||||
"""
|
||||
kwargs = {k: v for (k, v) in
|
||||
dict(suffix=suffix, prefix=prefix, dir=dir).items()
|
||||
if v is not None}
|
||||
return old_mkdtemp(**kwargs)
|
||||
|
||||
else:
|
||||
from tempfile import mkdtemp
|
||||
|
||||
|
||||
# XXX backport: ResourceWarning was added in Python 3.2.
|
||||
# For earlier versions, fall back to RuntimeWarning instead.
|
||||
_ResourceWarning = RuntimeWarning if sys.version_info < (3, 2) else ResourceWarning
|
||||
|
||||
|
||||
class TemporaryDirectory(object):
|
||||
"""Create and return a temporary directory. This has the same
|
||||
behavior as mkdtemp but can be used as a context manager. For
|
||||
example:
|
||||
with TemporaryDirectory() as tmpdir:
|
||||
...
|
||||
Upon exiting the context, the directory and everything contained
|
||||
in it are removed.
|
||||
"""
|
||||
|
||||
def __init__(self, suffix=None, prefix=None, dir=None):
|
||||
self.name = mkdtemp(suffix, prefix, dir)
|
||||
self._finalizer = finalize(
|
||||
self, self._cleanup, self.name,
|
||||
warn_message="Implicitly cleaning up {!r}".format(self))
|
||||
|
||||
@classmethod
|
||||
def _cleanup(cls, name, warn_message):
|
||||
_rmtree(name)
|
||||
_warnings.warn(warn_message, _ResourceWarning)
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} {!r}>".format(self.__class__.__name__, self.name)
|
||||
|
||||
def __enter__(self):
|
||||
return self.name
|
||||
|
||||
def __exit__(self, exc, value, tb):
|
||||
self.cleanup()
|
||||
|
||||
def cleanup(self):
|
||||
if self._finalizer.detach():
|
||||
_rmtree(self.name)
|
|
@ -0,0 +1,148 @@
|
|||
"""
|
||||
https://github.com/pjdelport/backports.weakref/blob/master/src/backports/weakref.py
|
||||
Partial backport of Python 3.6's weakref module:
|
||||
finalize (new in Python 3.4)
|
||||
Backport modifications are marked with "XXX backport".
|
||||
"""
|
||||
from __future__ import absolute_import
|
||||
|
||||
import itertools
|
||||
import sys
|
||||
from weakref import ref
|
||||
|
||||
__all__ = ['finalize']
|
||||
|
||||
|
||||
class finalize(object):
|
||||
"""Class for finalization of weakrefable objects
|
||||
finalize(obj, func, *args, **kwargs) returns a callable finalizer
|
||||
object which will be called when obj is garbage collected. The
|
||||
first time the finalizer is called it evaluates func(*arg, **kwargs)
|
||||
and returns the result. After this the finalizer is dead, and
|
||||
calling it just returns None.
|
||||
When the program exits any remaining finalizers for which the
|
||||
atexit attribute is true will be run in reverse order of creation.
|
||||
By default atexit is true.
|
||||
"""
|
||||
|
||||
# Finalizer objects don't have any state of their own. They are
|
||||
# just used as keys to lookup _Info objects in the registry. This
|
||||
# ensures that they cannot be part of a ref-cycle.
|
||||
|
||||
__slots__ = ()
|
||||
_registry = {}
|
||||
_shutdown = False
|
||||
_index_iter = itertools.count()
|
||||
_dirty = False
|
||||
_registered_with_atexit = False
|
||||
|
||||
class _Info(object):
|
||||
__slots__ = ("weakref", "func", "args", "kwargs", "atexit", "index")
|
||||
|
||||
def __init__(self, obj, func, *args, **kwargs):
|
||||
if not self._registered_with_atexit:
|
||||
# We may register the exit function more than once because
|
||||
# of a thread race, but that is harmless
|
||||
import atexit
|
||||
atexit.register(self._exitfunc)
|
||||
finalize._registered_with_atexit = True
|
||||
info = self._Info()
|
||||
info.weakref = ref(obj, self)
|
||||
info.func = func
|
||||
info.args = args
|
||||
info.kwargs = kwargs or None
|
||||
info.atexit = True
|
||||
info.index = next(self._index_iter)
|
||||
self._registry[self] = info
|
||||
finalize._dirty = True
|
||||
|
||||
def __call__(self, _=None):
|
||||
"""If alive then mark as dead and return func(*args, **kwargs);
|
||||
otherwise return None"""
|
||||
info = self._registry.pop(self, None)
|
||||
if info and not self._shutdown:
|
||||
return info.func(*info.args, **(info.kwargs or {}))
|
||||
|
||||
def detach(self):
|
||||
"""If alive then mark as dead and return (obj, func, args, kwargs);
|
||||
otherwise return None"""
|
||||
info = self._registry.get(self)
|
||||
obj = info and info.weakref()
|
||||
if obj is not None and self._registry.pop(self, None):
|
||||
return (obj, info.func, info.args, info.kwargs or {})
|
||||
|
||||
def peek(self):
|
||||
"""If alive then return (obj, func, args, kwargs);
|
||||
otherwise return None"""
|
||||
info = self._registry.get(self)
|
||||
obj = info and info.weakref()
|
||||
if obj is not None:
|
||||
return (obj, info.func, info.args, info.kwargs or {})
|
||||
|
||||
@property
|
||||
def alive(self):
|
||||
"""Whether finalizer is alive"""
|
||||
return self in self._registry
|
||||
|
||||
@property
|
||||
def atexit(self):
|
||||
"""Whether finalizer should be called at exit"""
|
||||
info = self._registry.get(self)
|
||||
return bool(info) and info.atexit
|
||||
|
||||
@atexit.setter
|
||||
def atexit(self, value):
|
||||
info = self._registry.get(self)
|
||||
if info:
|
||||
info.atexit = bool(value)
|
||||
|
||||
def __repr__(self):
|
||||
info = self._registry.get(self)
|
||||
obj = info and info.weakref()
|
||||
if obj is None:
|
||||
return '<%s object at %#x; dead>' % (type(self).__name__, id(self))
|
||||
else:
|
||||
return '<%s object at %#x; for %r at %#x>' % \
|
||||
(type(self).__name__, id(self), type(obj).__name__, id(obj))
|
||||
|
||||
@classmethod
|
||||
def _select_for_exit(cls):
|
||||
# Return live finalizers marked for exit, oldest first
|
||||
L = [(f,i) for (f,i) in cls._registry.items() if i.atexit]
|
||||
L.sort(key=lambda item:item[1].index)
|
||||
return [f for (f,i) in L]
|
||||
|
||||
@classmethod
|
||||
def _exitfunc(cls):
|
||||
# At shutdown invoke finalizers for which atexit is true.
|
||||
# This is called once all other non-daemonic threads have been
|
||||
# joined.
|
||||
reenable_gc = False
|
||||
try:
|
||||
if cls._registry:
|
||||
import gc
|
||||
if gc.isenabled():
|
||||
reenable_gc = True
|
||||
gc.disable()
|
||||
pending = None
|
||||
while True:
|
||||
if pending is None or finalize._dirty:
|
||||
pending = cls._select_for_exit()
|
||||
finalize._dirty = False
|
||||
if not pending:
|
||||
break
|
||||
f = pending.pop()
|
||||
try:
|
||||
# gc is disabled, so (assuming no daemonic
|
||||
# threads) the following is the only line in
|
||||
# this function which might trigger creation
|
||||
# of a new finalizer
|
||||
f()
|
||||
except Exception:
|
||||
sys.excepthook(*sys.exc_info())
|
||||
assert f not in cls._registry
|
||||
finally:
|
||||
# prevent any more finalizers from executing during shutdown
|
||||
finalize._shutdown = True
|
||||
if reenable_gc:
|
||||
gc.enable()
|
|
@ -840,7 +840,7 @@ namespace trezor{
|
|||
throw exc::DeviceAcquireException("Unable to claim libusb device");
|
||||
}
|
||||
|
||||
m_conn_count += 1;
|
||||
m_conn_count = 1;
|
||||
m_proto->session_begin(*this);
|
||||
|
||||
#undef TREZOR_DESTROY_SESSION
|
||||
|
|
|
@ -3790,6 +3790,7 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
|
|||
{
|
||||
auto rc = tools::wallet2::make_new(vm, false, password_prompter);
|
||||
m_wallet = std::move(rc.first);
|
||||
m_wallet->callback(this);
|
||||
if (!m_wallet)
|
||||
{
|
||||
return {};
|
||||
|
@ -3807,9 +3808,11 @@ boost::optional<epee::wipeable_string> simple_wallet::new_wallet(const boost::pr
|
|||
m_wallet->set_refresh_from_block_height(m_restore_height);
|
||||
|
||||
auto device_desc = tools::wallet2::device_name_option(vm);
|
||||
auto device_derivation_path = tools::wallet2::device_derivation_path_option(vm);
|
||||
try
|
||||
{
|
||||
bool create_address_file = command_line::get_arg(vm, arg_create_address_file);
|
||||
m_wallet->device_derivation_path(device_derivation_path);
|
||||
m_wallet->restore(m_wallet_file, std::move(rc.second).password(), device_desc.empty() ? "Ledger" : device_desc, create_address_file);
|
||||
message_writer(console_color_white, true) << tr("Generated new wallet on hw device: ")
|
||||
<< m_wallet->get_account().get_public_address_str(m_wallet->nettype());
|
||||
|
@ -3897,7 +3900,7 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
|
|||
epee::wipeable_string password;
|
||||
try
|
||||
{
|
||||
auto rc = tools::wallet2::make_from_file(vm, false, m_wallet_file, password_prompter);
|
||||
auto rc = tools::wallet2::make_from_file(vm, false, "", password_prompter);
|
||||
m_wallet = std::move(rc.first);
|
||||
password = std::move(std::move(rc.second).password());
|
||||
if (!m_wallet)
|
||||
|
@ -3905,6 +3908,8 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm)
|
|||
return false;
|
||||
}
|
||||
|
||||
m_wallet->callback(this);
|
||||
m_wallet->load(m_wallet_file, password);
|
||||
std::string prefix;
|
||||
bool ready;
|
||||
uint32_t threshold, total;
|
||||
|
@ -4308,6 +4313,61 @@ boost::optional<epee::wipeable_string> simple_wallet::on_get_password(const char
|
|||
return pwd_container->password();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void simple_wallet::on_button_request()
|
||||
{
|
||||
message_writer(console_color_white, false) << tr("Device requires attention");
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void simple_wallet::on_pin_request(epee::wipeable_string & pin)
|
||||
{
|
||||
#ifdef HAVE_READLINE
|
||||
rdln::suspend_readline pause_readline;
|
||||
#endif
|
||||
std::string msg = tr("Enter device PIN");
|
||||
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
|
||||
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device PIN"));
|
||||
pin = pwd_container->password();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void simple_wallet::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
|
||||
{
|
||||
if (on_device){
|
||||
message_writer(console_color_white, true) << tr("Please enter the device passphrase on the device");
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef HAVE_READLINE
|
||||
rdln::suspend_readline pause_readline;
|
||||
#endif
|
||||
std::string msg = tr("Enter device passphrase");
|
||||
auto pwd_container = tools::password_container::prompt(false, msg.c_str());
|
||||
THROW_WALLET_EXCEPTION_IF(!pwd_container, tools::error::password_entry_failed, tr("Failed to read device passphrase"));
|
||||
passphrase = pwd_container->password();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void simple_wallet::on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money)
|
||||
{
|
||||
// Key image sync after the first refresh
|
||||
if (!m_wallet->get_account().get_device().has_tx_cold_sign()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!received_money || m_wallet->get_device_last_key_image_sync() != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Finished first refresh for HW device and money received -> KI sync
|
||||
message_writer() << "\n" << tr("The first refresh has finished for the HW-based wallet with received money. hw_key_images_sync is needed. ");
|
||||
|
||||
std::string accepted = input_line(tr("Do you want to do it now? (Y/Yes/N/No): "));
|
||||
if (std::cin.eof() || !command_line::is_yes(accepted)) {
|
||||
message_writer(console_color_red, false) << tr("hw_key_images_sync skipped. Run command manually before a transfer.");
|
||||
return;
|
||||
}
|
||||
|
||||
key_images_sync_intern();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bool is_init)
|
||||
{
|
||||
if (!try_connect_to_daemon(is_init))
|
||||
|
@ -4325,13 +4385,14 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
|
|||
message_writer() << tr("Starting refresh...");
|
||||
|
||||
uint64_t fetched_blocks = 0;
|
||||
bool received_money = false;
|
||||
bool ok = false;
|
||||
std::ostringstream ss;
|
||||
try
|
||||
{
|
||||
m_in_manual_refresh.store(true, std::memory_order_relaxed);
|
||||
epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){m_in_manual_refresh.store(false, std::memory_order_relaxed);});
|
||||
m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks);
|
||||
m_wallet->refresh(m_wallet->is_trusted_daemon(), start_height, fetched_blocks, received_money);
|
||||
ok = true;
|
||||
// Clear line "Height xxx of xxx"
|
||||
std::cout << "\r \r";
|
||||
|
@ -4339,6 +4400,7 @@ bool simple_wallet::refresh_main(uint64_t start_height, enum ResetType reset, bo
|
|||
if (is_init)
|
||||
print_accounts();
|
||||
show_balance_unlocked();
|
||||
on_refresh_finished(start_height, fetched_blocks, is_init, received_money);
|
||||
}
|
||||
catch (const tools::error::daemon_busy&)
|
||||
{
|
||||
|
@ -8096,13 +8158,13 @@ bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &args)
|
|||
fail_msg_writer() << tr("hw wallet does not support cold KI sync");
|
||||
return true;
|
||||
}
|
||||
if (!m_wallet->is_trusted_daemon())
|
||||
{
|
||||
fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon");
|
||||
return true;
|
||||
}
|
||||
|
||||
LOCK_IDLE_SCOPE();
|
||||
key_images_sync_intern();
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void simple_wallet::key_images_sync_intern(){
|
||||
try
|
||||
{
|
||||
message_writer(console_color_white, false) << tr("Please confirm the key image sync on the device");
|
||||
|
@ -8111,19 +8173,23 @@ bool simple_wallet::hw_key_images_sync(const std::vector<std::string> &args)
|
|||
uint64_t height = m_wallet->cold_key_image_sync(spent, unspent);
|
||||
if (height > 0)
|
||||
{
|
||||
success_msg_writer() << tr("Signed key images imported to height ") << height << ", "
|
||||
<< print_money(spent) << tr(" spent, ") << print_money(unspent) << tr(" unspent");
|
||||
} else {
|
||||
success_msg_writer() << tr("Key images synchronized to height ") << height;
|
||||
if (!m_wallet->is_trusted_daemon())
|
||||
{
|
||||
message_writer() << tr("Running untrusted daemon, cannot determine which transaction output is spent. Use a trusted daemon with --trusted-daemon and run rescan_spent");
|
||||
} else
|
||||
{
|
||||
success_msg_writer() << print_money(spent) << tr(" spent, ") << print_money(unspent) << tr(" unspent");
|
||||
}
|
||||
}
|
||||
else {
|
||||
fail_msg_writer() << tr("Failed to import key images");
|
||||
}
|
||||
}
|
||||
catch (const std::exception &e)
|
||||
{
|
||||
fail_msg_writer() << tr("Failed to import key images: ") << e.what();
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
bool simple_wallet::hw_reconnect(const std::vector<std::string> &args)
|
||||
|
|
|
@ -241,6 +241,8 @@ namespace cryptonote
|
|||
bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr);
|
||||
std::string get_prompt() const;
|
||||
bool print_seed(bool encrypted);
|
||||
void key_images_sync_intern();
|
||||
void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money);
|
||||
|
||||
struct transfer_view
|
||||
{
|
||||
|
@ -287,6 +289,9 @@ namespace cryptonote
|
|||
virtual void on_money_spent(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& in_tx, uint64_t amount, const cryptonote::transaction& spend_tx, const cryptonote::subaddress_index& subaddr_index);
|
||||
virtual void on_skip_transaction(uint64_t height, const crypto::hash &txid, const cryptonote::transaction& tx);
|
||||
virtual boost::optional<epee::wipeable_string> on_get_password(const char *reason);
|
||||
virtual void on_button_request();
|
||||
virtual void on_pin_request(epee::wipeable_string & pin);
|
||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
|
||||
//----------------------------------------------------------
|
||||
|
||||
friend class refresh_progress_reporter_t;
|
||||
|
|
|
@ -222,6 +222,7 @@ struct options {
|
|||
};
|
||||
const command_line::arg_descriptor<uint64_t> kdf_rounds = {"kdf-rounds", tools::wallet2::tr("Number of rounds for the key derivation function"), 1};
|
||||
const command_line::arg_descriptor<std::string> hw_device = {"hw-device", tools::wallet2::tr("HW device to use"), ""};
|
||||
const command_line::arg_descriptor<std::string> hw_device_derivation_path = {"hw-device-deriv-path", tools::wallet2::tr("HW device wallet derivation path (e.g., SLIP-10)"), ""};
|
||||
const command_line::arg_descriptor<std::string> tx_notify = { "tx-notify" , "Run a program for each new incoming transaction, '%s' will be replaced by the transaction hash" , "" };
|
||||
};
|
||||
|
||||
|
@ -274,6 +275,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
|
|||
auto daemon_host = command_line::get_arg(vm, opts.daemon_host);
|
||||
auto daemon_port = command_line::get_arg(vm, opts.daemon_port);
|
||||
auto device_name = command_line::get_arg(vm, opts.hw_device);
|
||||
auto device_derivation_path = command_line::get_arg(vm, opts.hw_device_derivation_path);
|
||||
|
||||
THROW_WALLET_EXCEPTION_IF(!daemon_address.empty() && !daemon_host.empty() && 0 != daemon_port,
|
||||
tools::error::wallet_internal_error, tools::wallet2::tr("can't specify daemon host or port more than once"));
|
||||
|
@ -329,6 +331,7 @@ std::unique_ptr<tools::wallet2> make_basic(const boost::program_options::variabl
|
|||
boost::filesystem::path ringdb_path = command_line::get_arg(vm, opts.shared_ringdb_dir);
|
||||
wallet->set_ring_database(ringdb_path.string());
|
||||
wallet->device_name(device_name);
|
||||
wallet->device_derivation_path(device_derivation_path);
|
||||
|
||||
try
|
||||
{
|
||||
|
@ -838,6 +841,24 @@ wallet_keys_unlocker::~wallet_keys_unlocker()
|
|||
}
|
||||
}
|
||||
|
||||
void wallet_device_callback::on_button_request()
|
||||
{
|
||||
if (wallet)
|
||||
wallet->on_button_request();
|
||||
}
|
||||
|
||||
void wallet_device_callback::on_pin_request(epee::wipeable_string & pin)
|
||||
{
|
||||
if (wallet)
|
||||
wallet->on_pin_request(pin);
|
||||
}
|
||||
|
||||
void wallet_device_callback::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
|
||||
{
|
||||
if (wallet)
|
||||
wallet->on_passphrase_request(on_device, passphrase);
|
||||
}
|
||||
|
||||
wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
|
||||
m_multisig_rescan_info(NULL),
|
||||
m_multisig_rescan_k(NULL),
|
||||
|
@ -891,7 +912,8 @@ wallet2::wallet2(network_type nettype, uint64_t kdf_rounds, bool unattended):
|
|||
m_ringdb(),
|
||||
m_last_block_reward(0),
|
||||
m_encrypt_keys_after_refresh(boost::none),
|
||||
m_unattended(unattended)
|
||||
m_unattended(unattended),
|
||||
m_device_last_key_image_sync(0)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -914,6 +936,11 @@ std::string wallet2::device_name_option(const boost::program_options::variables_
|
|||
return command_line::get_arg(vm, options().hw_device);
|
||||
}
|
||||
|
||||
std::string wallet2::device_derivation_path_option(const boost::program_options::variables_map &vm)
|
||||
{
|
||||
return command_line::get_arg(vm, options().hw_device_derivation_path);
|
||||
}
|
||||
|
||||
void wallet2::init_options(boost::program_options::options_description& desc_params)
|
||||
{
|
||||
const options opts{};
|
||||
|
@ -930,6 +957,7 @@ void wallet2::init_options(boost::program_options::options_description& desc_par
|
|||
command_line::add_arg(desc_params, opts.shared_ringdb_dir);
|
||||
command_line::add_arg(desc_params, opts.kdf_rounds);
|
||||
command_line::add_arg(desc_params, opts.hw_device);
|
||||
command_line::add_arg(desc_params, opts.hw_device_derivation_path);
|
||||
command_line::add_arg(desc_params, opts.tx_notify);
|
||||
}
|
||||
|
||||
|
@ -949,7 +977,7 @@ std::pair<std::unique_ptr<wallet2>, password_container> wallet2::make_from_file(
|
|||
return {nullptr, password_container{}};
|
||||
}
|
||||
auto wallet = make_basic(vm, unattended, opts, password_prompter);
|
||||
if (wallet)
|
||||
if (wallet && !wallet_file.empty())
|
||||
{
|
||||
wallet->load(wallet_file, pwd->password());
|
||||
}
|
||||
|
@ -1091,15 +1119,17 @@ bool wallet2::reconnect_device()
|
|||
hw::device &hwdev = lookup_device(m_device_name);
|
||||
hwdev.set_name(m_device_name);
|
||||
hwdev.set_network_type(m_nettype);
|
||||
hwdev.set_derivation_path(m_device_derivation_path);
|
||||
hwdev.set_callback(get_device_callback());
|
||||
r = hwdev.init();
|
||||
if (!r){
|
||||
LOG_PRINT_L2("Could not init device");
|
||||
MERROR("Could not init device");
|
||||
return false;
|
||||
}
|
||||
|
||||
r = hwdev.connect();
|
||||
if (!r){
|
||||
LOG_PRINT_L2("Could not connect to the device");
|
||||
MERROR("Could not connect to the device");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -2998,6 +3028,7 @@ bool wallet2::clear()
|
|||
m_subaddresses.clear();
|
||||
m_subaddress_labels.clear();
|
||||
m_multisig_rounds_passed = 0;
|
||||
m_device_last_key_image_sync = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -3159,6 +3190,9 @@ bool wallet2::store_keys(const std::string& keys_file_name, const epee::wipeable
|
|||
value.SetString(m_device_name.c_str(), m_device_name.size());
|
||||
json.AddMember("device_name", value, json.GetAllocator());
|
||||
|
||||
value.SetString(m_device_derivation_path.c_str(), m_device_derivation_path.size());
|
||||
json.AddMember("device_derivation_path", value, json.GetAllocator());
|
||||
|
||||
// Serialize the JSON object
|
||||
rapidjson::StringBuffer buffer;
|
||||
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
|
||||
|
@ -3278,6 +3312,7 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||
m_subaddress_lookahead_major = SUBADDRESS_LOOKAHEAD_MAJOR;
|
||||
m_subaddress_lookahead_minor = SUBADDRESS_LOOKAHEAD_MINOR;
|
||||
m_device_name = "";
|
||||
m_device_derivation_path = "";
|
||||
m_key_device_type = hw::device::device_type::SOFTWARE;
|
||||
encrypted_secret_keys = false;
|
||||
}
|
||||
|
@ -3445,6 +3480,9 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||
m_device_name = m_key_device_type == hw::device::device_type::LEDGER ? "Ledger" : "default";
|
||||
}
|
||||
}
|
||||
|
||||
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, device_derivation_path, std::string, String, false, std::string());
|
||||
m_device_derivation_path = field_device_derivation_path;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -3459,6 +3497,8 @@ bool wallet2::load_keys(const std::string& keys_file_name, const epee::wipeable_
|
|||
hw::device &hwdev = lookup_device(m_device_name);
|
||||
THROW_WALLET_EXCEPTION_IF(!hwdev.set_name(m_device_name), error::wallet_internal_error, "Could not set device name " + m_device_name);
|
||||
hwdev.set_network_type(m_nettype);
|
||||
hwdev.set_derivation_path(m_device_derivation_path);
|
||||
hwdev.set_callback(get_device_callback());
|
||||
THROW_WALLET_EXCEPTION_IF(!hwdev.init(), error::wallet_internal_error, "Could not initialize the device " + m_device_name);
|
||||
THROW_WALLET_EXCEPTION_IF(!hwdev.connect(), error::wallet_internal_error, "Could not connect to the device " + m_device_name);
|
||||
m_account.set_device(hwdev);
|
||||
|
@ -3965,6 +4005,8 @@ void wallet2::restore(const std::string& wallet_, const epee::wipeable_string& p
|
|||
auto &hwdev = lookup_device(device_name);
|
||||
hwdev.set_name(device_name);
|
||||
hwdev.set_network_type(m_nettype);
|
||||
hwdev.set_derivation_path(m_device_derivation_path);
|
||||
hwdev.set_callback(get_device_callback());
|
||||
|
||||
m_account.create_from_device(hwdev);
|
||||
m_key_device_type = m_account.get_device().get_type();
|
||||
|
@ -9238,9 +9280,7 @@ void wallet2::cold_sign_tx(const std::vector<pending_tx>& ptx_vector, signed_tx_
|
|||
//----------------------------------------------------------------------------------------------------
|
||||
uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
|
||||
auto & hwdev = get_account().get_device();
|
||||
if (!hwdev.has_ki_cold_sync()){
|
||||
throw std::invalid_argument("Device does not support cold ki sync protocol");
|
||||
}
|
||||
CHECK_AND_ASSERT_THROW_MES(hwdev.has_ki_cold_sync(), "Device does not support cold ki sync protocol");
|
||||
|
||||
auto dev_cold = dynamic_cast<::hw::device_cold*>(&hwdev);
|
||||
CHECK_AND_ASSERT_THROW_MES(dev_cold, "Device does not implement cold signing interface");
|
||||
|
@ -9251,7 +9291,11 @@ uint64_t wallet2::cold_key_image_sync(uint64_t &spent, uint64_t &unspent) {
|
|||
|
||||
dev_cold->ki_sync(&wallet_shim, m_transfers, ski);
|
||||
|
||||
return import_key_images(ski, 0, spent, unspent);
|
||||
// Call COMMAND_RPC_IS_KEY_IMAGE_SPENT only if daemon is trusted.
|
||||
uint64_t import_res = import_key_images(ski, 0, spent, unspent, is_trusted_daemon());
|
||||
m_device_last_key_image_sync = time(NULL);
|
||||
|
||||
return import_res;
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::get_hard_fork_info(uint8_t version, uint64_t &earliest_height) const
|
||||
|
@ -11985,4 +12029,29 @@ uint64_t wallet2::get_segregation_fork_height() const
|
|||
void wallet2::generate_genesis(cryptonote::block& b) const {
|
||||
cryptonote::generate_genesis_block(b, get_config(m_nettype).GENESIS_TX, get_config(m_nettype).GENESIS_NONCE);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
wallet_device_callback * wallet2::get_device_callback()
|
||||
{
|
||||
if (!m_device_callback){
|
||||
m_device_callback.reset(new wallet_device_callback(this));
|
||||
}
|
||||
return m_device_callback.get();
|
||||
}//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::on_button_request()
|
||||
{
|
||||
if (0 != m_callback)
|
||||
m_callback->on_button_request();
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::on_pin_request(epee::wipeable_string & pin)
|
||||
{
|
||||
if (0 != m_callback)
|
||||
m_callback->on_pin_request(pin);
|
||||
}
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
void wallet2::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase)
|
||||
{
|
||||
if (0 != m_callback)
|
||||
m_callback->on_passphrase_request(on_device, passphrase);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -100,11 +100,26 @@ namespace tools
|
|||
virtual void on_lw_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||
virtual void on_lw_unconfirmed_money_received(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||
virtual void on_lw_money_spent(uint64_t height, const crypto::hash &txid, uint64_t amount) {}
|
||||
// Device callbacks
|
||||
virtual void on_button_request() {}
|
||||
virtual void on_pin_request(epee::wipeable_string & pin) {}
|
||||
virtual void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) {}
|
||||
// Common callbacks
|
||||
virtual void on_pool_tx_removed(const crypto::hash &txid) {}
|
||||
virtual ~i_wallet2_callback() {}
|
||||
};
|
||||
|
||||
class wallet_device_callback : public hw::i_device_callback
|
||||
{
|
||||
public:
|
||||
wallet_device_callback(wallet2 * wallet): wallet(wallet) {};
|
||||
void on_button_request() override;
|
||||
void on_pin_request(epee::wipeable_string & pin) override;
|
||||
void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) override;
|
||||
private:
|
||||
wallet2 * wallet;
|
||||
};
|
||||
|
||||
struct tx_dust_policy
|
||||
{
|
||||
uint64_t dust_threshold;
|
||||
|
@ -156,6 +171,7 @@ namespace tools
|
|||
{
|
||||
friend class ::Serialization_portability_wallet_Test;
|
||||
friend class wallet_keys_unlocker;
|
||||
friend class wallet_device_callback;
|
||||
public:
|
||||
static constexpr const std::chrono::seconds rpc_timeout = std::chrono::minutes(3) + std::chrono::seconds(30);
|
||||
|
||||
|
@ -177,6 +193,7 @@ namespace tools
|
|||
static bool has_testnet_option(const boost::program_options::variables_map& vm);
|
||||
static bool has_stagenet_option(const boost::program_options::variables_map& vm);
|
||||
static std::string device_name_option(const boost::program_options::variables_map& vm);
|
||||
static std::string device_derivation_path_option(const boost::program_options::variables_map &vm);
|
||||
static void init_options(boost::program_options::options_description& desc_params);
|
||||
|
||||
//! Uses stdin and stdout. Returns a wallet2 if no errors.
|
||||
|
@ -795,6 +812,7 @@ namespace tools
|
|||
bool is_transfer_unlocked(uint64_t unlock_time, uint64_t block_height) const;
|
||||
|
||||
uint64_t get_last_block_reward() const { return m_last_block_reward; }
|
||||
uint64_t get_device_last_key_image_sync() const { return m_device_last_key_image_sync; }
|
||||
|
||||
template <class t_archive>
|
||||
inline void serialize(t_archive &a, const unsigned int ver)
|
||||
|
@ -903,6 +921,9 @@ namespace tools
|
|||
if(ver < 26)
|
||||
return;
|
||||
a & m_tx_device;
|
||||
if(ver < 27)
|
||||
return;
|
||||
a & m_device_last_key_image_sync;
|
||||
}
|
||||
|
||||
/*!
|
||||
|
@ -964,6 +985,8 @@ namespace tools
|
|||
void confirm_non_default_ring_size(bool always) { m_confirm_non_default_ring_size = always; }
|
||||
const std::string & device_name() const { return m_device_name; }
|
||||
void device_name(const std::string & device_name) { m_device_name = device_name; }
|
||||
const std::string & device_derivation_path() const { return m_device_derivation_path; }
|
||||
void device_derivation_path(const std::string &device_derivation_path) { m_device_derivation_path = device_derivation_path; }
|
||||
|
||||
bool get_tx_key(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);
|
||||
|
@ -1287,6 +1310,11 @@ namespace tools
|
|||
void setup_new_blockchain();
|
||||
void create_keys_file(const std::string &wallet_, bool watch_only, const epee::wipeable_string &password, bool create_address_file);
|
||||
|
||||
wallet_device_callback * get_device_callback();
|
||||
void on_button_request();
|
||||
void on_pin_request(epee::wipeable_string & pin);
|
||||
void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase);
|
||||
|
||||
cryptonote::account_base m_account;
|
||||
boost::optional<epee::net_utils::http::login> m_daemon_login;
|
||||
std::string m_daemon_address;
|
||||
|
@ -1365,6 +1393,8 @@ namespace tools
|
|||
std::unordered_set<crypto::hash> m_scanned_pool_txs[2];
|
||||
size_t m_subaddress_lookahead_major, m_subaddress_lookahead_minor;
|
||||
std::string m_device_name;
|
||||
std::string m_device_derivation_path;
|
||||
uint64_t m_device_last_key_image_sync;
|
||||
|
||||
// Aux transaction data from device
|
||||
std::unordered_map<crypto::hash, std::string> m_tx_device;
|
||||
|
@ -1398,9 +1428,10 @@ namespace tools
|
|||
bool m_devices_registered;
|
||||
|
||||
std::shared_ptr<tools::Notify> m_tx_notify;
|
||||
std::unique_ptr<wallet_device_callback> m_device_callback;
|
||||
};
|
||||
}
|
||||
BOOST_CLASS_VERSION(tools::wallet2, 26)
|
||||
BOOST_CLASS_VERSION(tools::wallet2, 27)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 10)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
|
||||
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
|
||||
|
|
|
@ -219,6 +219,14 @@ namespace tools
|
|||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
struct password_entry_failed : public wallet_runtime_error
|
||||
{
|
||||
explicit password_entry_failed(std::string&& loc, const std::string &msg = "Password entry failed")
|
||||
: wallet_runtime_error(std::move(loc), msg)
|
||||
{
|
||||
}
|
||||
};
|
||||
//----------------------------------------------------------------------------------------------------
|
||||
const char* const file_error_messages[] = {
|
||||
"file already exists",
|
||||
"file not found",
|
||||
|
|
Loading…
Reference in New Issue