Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ledger Nano S #3303

Merged
merged 1 commit into from
Mar 4, 2018
Merged

Ledger Nano S #3303

merged 1 commit into from
Mar 4, 2018

Conversation

cslashm
Copy link
Contributor

@cslashm cslashm commented Feb 21, 2018

This a new push request for Hardware Wallet support in Monero client.

It replaces the push request #3095

Goals

The goal of this PR is to propose code modifications to integrate the ledger HW into
monero-wallet-cli.

Approach

The idea relies on 2 main principles:

  • master spend key and view key never leave the device
  • all other needed secret are computed by the device but stored encrypted by the Monero client

The basic approach it to delegate all sensitive data (master key, secret ephemeral key,
key derivation, ....) and related operations to the device. As the device as low
memory the device do not keep itself the values (except for view/spend keys) but once
computed there are encrypted (AES) and return back to monero-wallet-cli. When
they need to be manipulated by the device, they are decrypted on receive.

Moreover using the client for storing the value in encrypted form limits the modification
in the client code. Those values are transfered from one C-structure to another one
as previously.

The code is modified at tow main levels:

. crypto operations, mainly crypto.cpp and cryptonote_format_utils.cpp
. signature level, mainly rctSigs.cpp and cryptonote_tx_utils.cpp

The pdf (https://github.com/LedgerHQ/blue-app-monero/blob/master/doc/developer/blue-app-monero.pdf) gives more details (but is a little be obsolete)

The application device is located at https://github.com/LedgerHQ/blue-app-monero.

Open Integration

The integration has been done with the wishes to be open to any other hardware wallet.
To achieve that a C++ class hw::Device has been introduce. All the Monero
client code has been modified to use this new interface.

Two initial implementation are provided:

  • the "default", which remaps all call to initial Monero code
  • the "ledger", which delegates all call to Ledger device.

Now any HW that provide a class Device implementation would be defacto
supported by the Monero client.

What works

The today code support sending and receiving moneroj. It support main and sub-address.

In the "soon" future, it will support proof of send, proof of reserve.

Then will come other stuffs.

@dEBRUYNE-1
Copy link
Contributor

Referencing #3095 if anyone is interested in previous discussion.

@PabraiVentures
Copy link

Is this production tested and ready for general use?

@anonimal
Copy link
Contributor

push request

@iDunk5400
Copy link
Contributor

unit_tests should not crash if a device is not connected.

GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from build/release/tests/unit_tests/unit_tests...done.
[New LWP 21661]
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
Core was generated by `/home/monero/build/release/tests/unit_tests/unit_tests --data-dir /'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  __GI___libc_free (mem=0x725eb8bd) at malloc.c:2951
  Id   Target Id         Frame 
* 1    Thread 0x7f154c32b780 (LWP 21661) __GI___libc_free (mem=0x725eb8bd) at malloc.c:2951

Thread 1 (Thread 0x7f154c32b780 (LWP 21661)):
#0  __GI___libc_free (mem=0x725eb8bd) at malloc.c:2951
#1  0x00007f154b6df2bd in SCardFreeMemory () from /lib/x86_64-linux-gnu/libpcsclite.so.1
#2  0x000055aacf779d8a in hw::ledger::DeviceLedger::connect() ()
#3  0x000055aacf5a2327 in tools::wallet2::load_keys(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, epee::wipeable_string const&) ()
#4  0x000055aacf5edc8f in tools::wallet2::load(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, epee::wipeable_string const&) ()
#5  0x000055aacf30497f in Serialization_portability_wallet_Test::TestBody() ()
#6  0x000055aacf6e7c99 in void testing::internal::HandleSehExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) ()
#7  0x000055aacf6e1229 in void testing::internal::HandleExceptionsInMethodIfSupported<testing::Test, void>(testing::Test*, void (testing::Test::*)(), char const*) ()
#8  0x000055aacf6c4108 in testing::Test::Run() ()
#9  0x000055aacf6c4abe in testing::TestInfo::Run() ()
#10 0x000055aacf6c51e3 in testing::TestCase::Run() ()
#11 0x000055aacf6cc576 in testing::internal::UnitTestImpl::RunAllTests() ()
#12 0x000055aacf6e9279 in bool testing::internal::HandleSehExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) ()
#13 0x000055aacf6e20a1 in bool testing::internal::HandleExceptionsInMethodIfSupported<testing::internal::UnitTestImpl, bool>(testing::internal::UnitTestImpl*, bool (testing::internal::UnitTestImpl::*)(), char const*) ()
#14 0x000055aacf6cafb8 in testing::UnitTest::Run() ()
#15 0x000055aacf0609f1 in main ()

target_link_libraries(device
PUBLIC
pcsclite
crypto
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crypto

${OPENSSL_CRYPTO_LIBRARIES}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added for next commit

@cslashm
Copy link
Contributor Author

cslashm commented Feb 22, 2018

@iDunk5400 strange, test should use the default device. Not NanoS one. I check that.

Edited:
I launched

 ./build/release/tests/unit_tests/unit_tests --data-dir /tmp

And it does'nt crash, and do not attemp any access to the device "ledger"

void derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res, hw::Device &device) {
device.derivation_to_scalar(derivation, output_index, res);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could all those be in a device file ? Or at least grouped together. Not a big thing though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

created a crypto_device.cpp.


void crypto_ops::derivation_to_scalar(const key_derivation &derivation, size_t output_index, ec_scalar &res) {
struct {
key_derivation derivation;
crypto::key_derivation derivation;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed ?

Copy link
Contributor Author

@cslashm cslashm Feb 22, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add this upon complier claimed it did not know the symbol else.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed


bool crypto_ops::derive_public_key(const key_derivation &derivation, size_t output_index,
const public_key &base, public_key &derived_key) {
ec_scalar scalar;
crypto::ec_scalar scalar;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same thing, will try to remove that tomorrow.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed


void crypto_ops::derive_secret_key(const key_derivation &derivation, size_t output_index,
const secret_key &base, secret_key &derived_key) {
ec_scalar scalar;
crypto::ec_scalar scalar;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

assert(sc_check(&base) == 0);
derivation_to_scalar(derivation, output_index, scalar);
sc_add(&derived_key, &base, &scalar);
hw::ledger::log_hexbuffer("crypto_ops::derive_secret_key: OUT : base", derived_key.data, 32);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be in a non-device function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, I miss it! Even after reading the diff twice :(

Sorry will be removed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

}
device.set_signature_mode(device.SIGNATURE_FAKE);
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Urgh. What is the purpose of these extra calls ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After re-reading, not needed. Related to previous thoughts.

Removed

blockchain_db
p2p
epee
device
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

o_O Surely this is not needed, right ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The linker make me crazy about think found or not. YES I need help on this point. Could someone check and try to clean the dependencies?

@@ -33,7 +33,7 @@
#include "crypto-tests.h"

bool check_scalar(const crypto::ec_scalar &scalar) {
return crypto::sc_check(crypto::operator &(scalar)) == 0;
return sc_check(crypto::operator &(scalar)) == 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

because of:

/home/cme/Projects/Git/Monero/monero-cslashm/tests/crypto/crypto.cpp:36:18: error: ‘sc_check’ is not a member of ‘crypto’
   return crypto::sc_check(crypto::operator &(scalar)) == 0;

Moreover sc_check is defined in src/crypto/crypto-ops.c outside any namspace. So I dont understand this crypto::

Do I miss something?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, it was compiling fine before, no ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest I do not remember as I had dis-activated test compilation for a long period.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That needs to be worked out. It should not be needed unless something broke.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see below

crypto::ge_tobytes(crypto::operator &(res), &point);
ge_p2 point;
ge_fromfe_frombytes_vartime(&point, reinterpret_cast<const unsigned char *>(&h));
ge_tobytes(crypto::operator &(res), &point);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, something broke and needs fixing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK I think I found and I'm right.

in src/crypto/crypto.cpp we have

namespace crypto {

  using std::abort;
  using std::int32_t;
  using std::int64_t;
  using std::size_t;
  using std::uint32_t;
  using std::uint64_t;

  extern "C" {
    #include "crypto-ops.h"
    #include "random.h"
  }
....

The file test/crypto/crypto.cpp directly includes src/crypto/crypto.cpp (which a bit "ugly" I think).
This inclusion seems to temporarily move crypto-ops definition to crypto namespace

In my PR "crypto-ops.h" is incuded earlier pushing the definitions out of any namespace, as it should
according to namespace-less definition to crypto-ops.c/crypto-ops.h .

In conclusion I think this modifications should be keep.
(I also clean up my includes modification in the upcoming push update)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does crypto.h now include crypto-ops.h ? Is it really needed ? You seem to add just new prototypes, so th enew include doesn't seem needed at first glance, and it's apparently causing this namepace change.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new/moved API use ge_p3

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've waded through this code, and I see now how it is. I'm fine with those changes.

crypto::hash_to_ec(key, tmp);
crypto::ge_p3_tobytes(crypto::operator &(res), &tmp);
ge_p3_tobytes(crypto::operator &(res), &tmp);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why all the namespace changes ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

cslashm added a commit to cslashm/monero that referenced this pull request Feb 23, 2018
@fluffypony
Copy link
Contributor

@cslashm to save you guys the hassle of having to rebase we've temporarily paused merging PRs, do you think you'll be able to do the last few tweaks to this on Monday? That way we don't leave things frozen for too long:)

@cslashm
Copy link
Contributor Author

cslashm commented Feb 24, 2018 via email


namespace {
static std::map<std::string, Device&> registry;
static int inited = []() -> int {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put this into a function please. This is ripe for initialization order fiasco issues ... You'd have to move everything into a struct that has a single instance created in static memory, because otherwise the creation algorithm is racy between the two elements.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You mean you want I use a function call in main let's device_init() from main and totally discard the use of static lambda construction?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, I move the code in a hw::device_init() function. And I call this func from all main() functions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's "on_startup" in src/common/util.cpp for these things.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

got it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This does not solve the problem I was referencing. In C++, statics are initialized before main. If another constructor tries to ask for the device, and an object of that type is created in static memory, undefined behavior results. The only way to fix this specific problem is to do:

device& get_device(const std::string& name) {
    struct devices {
        devices() : device_map() { ... init... };

        std::map<std::string, std::unique_ptr<device> device_map;
    };
    static const devices instance;
    return instance.device_map.at(name);
}

Copy link
Contributor

@vtnerd vtnerd Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should note that no constructor is calling this before main. But its easy to add this and add hard to spot undefined behavior.

@@ -112,10 +112,14 @@ namespace rct {

//does a * G where a is a scalar and G is the curve basepoint
void scalarmultBase(key & aG, const key &a);
void scalarmultBase(key & aG, const key &a, hw::Device &device);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How are these redirects useful? Why wouldn't the places using them just call the function on the device directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see other comment (Moneromooo request + device routing)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See other responses about this topic


key scalarmultBase(const key & a, hw::Device &device) {
key aG;
device.scalarmultBase(aG, a);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't this function just return this value? Why the out parameter argument here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compute aG = a.G, as non device function.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why doesn't the virtual method return the value like this function does?

}

secret_key generate_keys(public_key &pub, secret_key &sec, hw::Device &device) {
secret_key rng;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned this below, why these redirect functions? Why not just call the function directly?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the following reasons:

  • Upon @moneromooo-monero request who do not want modification in original low crypto functions
  • to manage the device abstraction between the various device implementations

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So wait, is the only reason to "blind" the device type in the function call? I.e. the header for the type does not have to be included in some other library, and instead forwards to here? There has to be a better arrangement for this ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are equivalent to the following ones defined in crypto.h/cpp and using underlaying device

static secret_key generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key = secret_key(), bool recover = false);
friend secret_key generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key, bool recover);


secret_key generate_keys(public_key &pub, secret_key &sec, hw::Device &device) {
secret_key rng;
device.generate_keys(pub, sec, secret_key(), false, rng);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the device returning secret keys? Is this the view key?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

secret key are returned AES encrypted as said in PR comment. View/Spend key are never returned, even encrypted. As reminder:

The basic approach it to delegate all sensitive data (master key, secret ephemeral key,
key derivation, ....) and related operations to the device. As the device as low
memory the device do not keep itself the values (except for view/spend keys) but once
computed there are encrypted (AES) and return back to monero-wallet-cli. When
they need to be manipulated by the device, they are decrypted on receive.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets starts with this - why are there two secrets keys here? What is being generated and returned by this call?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as said:

There are equivalent to the following ones defined in crypto.h/cpp and using underlaying device

static secret_key generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key = secret_key(), bool recover = false);
friend secret_key generate_keys(public_key &pub, secret_key &sec, const secret_key& recovery_key, bool recover);

#ifdef DEBUGLEDGER
//bool get_secret_keys(crypto::secret_key &viewkey , crypto::secret_key &spendkey);
#endif
bool generate_chacha_key(const cryptonote::account_keys &keys, crypto::chacha_key &key);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Theres lots to add in here, but add in the override tag to all of thee virtual functions so the compiler errors out if the base class interface changes. It will do this if the base class functions are pure virtual (because a definition is required), but otherwise this keyword helps subtle issues with interface changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override added everywhere in device_default and device_ledger

DeviceDefault(const DeviceDefault &device);
~DeviceDefault();

DeviceDefault& operator=(const DeviceDefault &device);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do the opposite, mark this as deleted: DeviceDefault& operator=(DeviceDefault const&) = delete;. The name of this class also breaks with existing style, but I guess @moneromooo-monero hasn't been concerned ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renaming done, a bit boring but done:

  • Device=>device
  • DeviceDefault => device_default
  • DeviceLedger => device_ledger

Copy link
Contributor Author

@cslashm cslashm Feb 26, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, If correctly understand I should mark all assignment copy as deleted to avoid their usage

Let me test that and think about it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

class DeviceDefault : public hw::Device {
public:
DeviceDefault();
DeviceDefault(const DeviceDefault &device);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mark as deleted, this time with DeviceDelete(DeviceDelete const&) = delete;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

MDEBUG( "Device "<<this->id <<" Created");
}

DeviceLedger::DeviceLedger(const DeviceLedger &device): DeviceLedger() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are the copy construtor and assignment ever used? I would just delete them, because its unlikely to be in use with this specific implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rereading all, yes copy/assignment constructors are no more used. I removed all of them by marking them
=delete

MDEBUG( "Device "<<this->id << " LOCKed");
}
void DeviceLedger::unlock_device() {
MDEBUG( "Ask for UNLOCKING for device "<<this->id);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't do this. It can throw, and leave the device locked. Best case this would have to be wrapped in its own try/catch block.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure to understand what you suggest. MDEBUG may throw exception?
So I modified with:

  try {
    MDEBUG( "Ask for UNLOCKING for device "<<this->id);
  } catch (...) {
  }

Copy link
Contributor

@vtnerd vtnerd Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes MDEBUG can throw in many different locations internally (it allocates memory, even if debug logging if off) .... yes I know.

@@ -1090,7 +1070,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
for (size_t i = 0; i < additional_tx_pub_keys.size(); ++i)
{
additional_derivations.push_back({});
if (!generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back()))
if (!generate_key_derivation(additional_tx_pub_keys[i], keys.m_view_secret_key, additional_derivations.back(),device))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Separate by one space, please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

U+1F612

for (size_t n = 0; n < tx.vout.size(); ++n)
{
const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target));
if (!out_key)
continue;

crypto::public_key derived_out_key;
bool r = derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key);
bool r = derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key,device);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

spacing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

U+1F612

@@ -486,6 +486,11 @@ namespace tools
void generate(const std::string& wallet, const epee::wipeable_string& password,
const cryptonote::account_public_address &account_public_address,
const crypto::secret_key& viewkey = crypto::secret_key());
/*!
*
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please complete the commentary, or remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

U+1F612

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry. I was trying to help.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is right, though. This is pointless AFAICT :P

static_assert(sizeof(chacha_key) <= sizeof(hash), "Size of hash must be at least that of chacha_key");
tools::scrubbed_arr<char, HASH_SIZE> pwd_hash;
crypto::cn_slow_hash(data, size, pwd_hash.data());
crypto::cn_slow_hash_pre(data, size, pwd_hash.data(), prehashed);
memcpy(&key, pwd_hash.data(), sizeof(key));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it might be clearer to have a separate generate_chacha_key_prehashed, and leave the current code untouched.

Copy link
Contributor

@m2049r m2049r Mar 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this breaks builds for arm & x86 as cn_slow_hash_pre() exists only for

#if !defined NO_AES && (defined(__x86_64__) || (defined(_MSC_VER) && defined(_WIN64)))

//-----------------------------------------------------------------
hw::device& account_keys::get_device() const {
return *m_device;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you try having a const/const and a non-const/non-const as suggested ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. Whatever the way i choose it implies too many modification. I really would like postpone this.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I'll look at it once it's merged.

void account_base::create_from_device(const std::string &device_name)
{

hw::device &hwdev = hw::get_device("ledger");// m_keys.get_device();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"ledger" should not be in the lib. If this is the device_name, use it. Otherwise, pass another parameter.

Copy link
Contributor Author

@cslashm cslashm Feb 27, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me see that, yes maybe here a rest of the first integration

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, You are right.

hwdev.init();
hwdev.connect();
hwdev.get_public_address(m_keys.m_account_address);
#ifdef DEBUGLEDGER
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick mass sed to DEBUG_HWDEVICE ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

crypto::secret_key m;
hwdev.get_subaddress_secret_key(a, index, m);
return m;
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have the same opinion as vtnerd, it would be better to keep the same prototype, just with an extra device parameter. But at this point I'm OK with leaving those if it's too much hassle.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will see. The waythe various subaddress functions are used and mixed make keeping the double prototype a bit hard and maybe dangerous. Need to think calmly about this.


target_link_libraries(device
PUBLIC
pcsclite
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still doesn't look optional to me.

char dd[32];
char logstr[128];

memmove(dd,d,len);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do not see the bounds checks though. Like if (len < 0 || len >= sizeof(dd)) do_something_errory();

@@ -33,7 +33,7 @@
#include "crypto-tests.h"

bool check_scalar(const crypto::ec_scalar &scalar) {
return crypto::sc_check(crypto::operator &(scalar)) == 0;
return sc_check(crypto::operator &(scalar)) == 0;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That needs to be worked out. It should not be needed unless something broke.

crypto::ge_tobytes(crypto::operator &(res), &point);
ge_p2 point;
ge_fromfe_frombytes_vartime(&point, reinterpret_cast<const unsigned char *>(&h));
ge_tobytes(crypto::operator &(res), &point);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same, something broke and needs fixing.

crypto::hash_to_ec(key, tmp);
crypto::ge_p3_tobytes(crypto::operator &(res), &tmp);
ge_p3_tobytes(crypto::operator &(res), &tmp);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

@cslashm
Copy link
Contributor Author

cslashm commented Feb 27, 2018 via email

/* SETUP */
/* ======================================================================= */

static std::map<std::string, device&> registry;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This needs to be std::unique_ptr<device> not device&. Otherwise we are constantly going to get memcheck complaints about this.

toHash[3 * i + 2] = aG[i];
toHash[3 * i + 3] = aHP[i];
rv.II[i] = scalarmultKey(Hi, xx[i]);
}
}
precomp(Ip[i].k, rv.II[i]);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to avoid breaking indent

Copy link
Contributor

@stoffu stoffu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good overall. Only minor comments.

@@ -515,8 +515,11 @@ void slow_hash_free_state(void)
* @param length the length in bytes of the data
* @param hash a pointer to a buffer in which the final 256 bit hash will be stored
*/
void cn_slow_hash(const void *data, size_t length, char *hash) {
cn_slow_hash_pre(data,length,hash,0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use false instead of 0

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

device_default(const device_default &device) = delete;
device_default& operator=(const device_default &device) = delete;

explicit operator bool() const { return false; };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

override

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


class Keymap {
public:
std::vector<struct ABPkeys> ABP;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use class to avoid a warning (or define ABPkeys as struct, or just write std::vector<ABPkeys>)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

of course! Dont know why struct...

done

this->buffer_send[offset] = 0x00;
offset += 1;
//index
static_assert(sizeof(cryptonote::subaddress_index) == 8);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

static_assert needs a second parameter which is a string describing the error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

this->buffer_send[offset] = 0x00;
offset += 1;
//index
static_assert(sizeof(cryptonote::subaddress_index) == 8);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return print_seed(false);
}

bool simple_wallet::encrypted_seed(const std::vector<std::string> &args/* = std::vector<std::string>()*/)
{
if (m_wallet->key_on_device())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test can be inserted to print_seed instead to take effect for both seed and encrypted_seed at the same time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added to print_seed

Copy link
Contributor

@stoffu stoffu Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I meant not only adding to print_seed, but also removing it from seed and encrypted_seed because they both call print_seed internally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, removed

test_tx,
test_ptx);
}
hwdev.set_signature_mode(hwdev.SIGNATURE_FAKE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hw::device::SIGNATURE_FAKE to explicitly tell that it's a static const

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}
hwdev.set_signature_mode(hwdev.SIGNATURE_FAKE);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like the same code needs to be added to create_transactions_from as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let me see that in detail; who call it ...

Maybe I should reject this command/function today

Copy link
Contributor

@stoffu stoffu Feb 28, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's needed for the sweep_all, sweep_below, sweep_single commands. You can add the same code to create_transactions_from just like you did to create_transactions_2. Very simple.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@@ -6819,6 +6850,8 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
unsigned int original_output_index = 0;
std::vector<size_t>* unused_transfers_indices = &unused_transfers_indices_per_subaddr[0].second;
std::vector<size_t>* unused_dust_indices = &unused_dust_indices_per_subaddr[0].second;
hw::device &hwdev = m_account.get_device();
hwdev.set_signature_mode(hwdev.SIGNATURE_FAKE);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hw::device::SIGNATURE_FAKE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

if ((!dsts.empty()) ||
(dsts.empty() && !(adding_fee || !preferred_inputs.empty() || should_pick_a_second_output(use_rct, txes.back().selected_transfers.size(), *unused_transfers_indices, *unused_dust_indices)) )
) {
hwdev.set_signature_mode(hwdev.SIGNATURE_REAL);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hw::device::SIGNATURE_REAL

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

@stoffu
Copy link
Contributor

stoffu commented Mar 2, 2018

Please don't forget to address this simple compile error:

src/wallet/api/wallet_manager.cpp:154:29: error: no matching member function for call to 'verify_password'
            return tools::wallet2::verify_password(keys_file_name, password, no_spend_key);

fail_msg_writer() << tr("command not supported by HW wallet");
return true;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You forgot to delete this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, removed

@stoffu
Copy link
Contributor

stoffu commented Mar 2, 2018

Please consider applying the following patch:

diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt
index cdb35d2a..26389220 100644
--- a/src/device/CMakeLists.txt
+++ b/src/device/CMakeLists.txt
@@ -67,7 +67,7 @@ monero_add_library(device
 target_link_libraries(device
   PUBLIC
     ${PCSC_LIBRARIES}
-    crypto
+    cncrypto
     ringct
     ${OPENSSL_CRYPTO_LIBRARIES}
     ${GNU_READLINE_LIBRARY}
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index d22ea5f7..58496f93 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -329,8 +329,15 @@ namespace hw {
 
       this->disconnect();
 
+#ifdef SCARD_AUTOALLOCATE
       dwReaders = SCARD_AUTOALLOCATE;
-      if ((rv = SCardListReaders(this->hContext, NULL, (LPSTR)&mszReaders, &dwReaders)) == SCARD_S_SUCCESS) {
+#else
+      rv = SCardListReaders(this->hContext, NULL, NULL, &dwReaders);
+      if (rv != SCARD_S_SUCCESS)
+        return false;
+      mszReaders = (LPSTR)calloc(dwReaders, sizeof(char));
+#endif
+      if ((rv = SCardListReaders(this->hContext, NULL, mszReaders, &dwReaders)) == SCARD_S_SUCCESS) {
         char* p;
         const char* prefix = this->name.c_str();
 
@@ -360,7 +367,11 @@ namespace hw {
       }
 
       if (mszReaders) {
+#ifdef SCARD_AUTOALLOCATE
         SCardFreeMemory(this->hContext, mszReaders);
+#else
+        free(mszReaders);
+#endif
         mszReaders = NULL;
       }
       if (rv != SCARD_S_SUCCESS) {
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index e6009517..02deee0c 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -3187,12 +3187,6 @@ bool simple_wallet::stop_mining(const std::vector<std::string>& args)
     return true;
   }
 
-  if (m_wallet->key_on_device())
-  {
-    fail_msg_writer() << tr("command not supported by HW wallet");
-    return true;
-  }
-
   COMMAND_RPC_STOP_MINING::request req;
   COMMAND_RPC_STOP_MINING::response res;
   bool r = net_utils::invoke_http_json("/stop_mining", req, res, m_http_client);
diff --git a/src/wallet/api/wallet_manager.cpp b/src/wallet/api/wallet_manager.cpp
index bb144227..9153eafc 100644
--- a/src/wallet/api/wallet_manager.cpp
+++ b/src/wallet/api/wallet_manager.cpp
@@ -151,7 +151,7 @@ bool WalletManagerImpl::walletExists(const std::string &path)
 
 bool WalletManagerImpl::verifyWalletPassword(const std::string &keys_file_name, const std::string &password, bool no_spend_key) const
 {
-           return tools::wallet2::verify_password(keys_file_name, password, no_spend_key);
+           return tools::wallet2::verify_password(keys_file_name, password, no_spend_key, hw::get_device("default"));
 }
 
 std::vector<std::string> WalletManagerImpl::findWallets(const std::string &path)

The only changes that may require a bit more care are those in ledger_device.cpp. The code was mostly copied from this tutorial site: https://ludovicrousseau.blogspot.jp/2010/04/pcsc-sample-in-c.html

Actually, the code on the website looks a lot similar to yours; perhaps did you also use this site when writing the code? Anyway, I noticed an oddity in your code where you (an the site) wrote

SCardListReaders(this->hContext, NULL, (LPSTR)&mszReaders, &dwReaders)

which is strange because mszReaders is already LPSTR (so &mszReaders would be LPSTR*). The above patch corrects this, please check.

@cslashm
Copy link
Contributor Author

cslashm commented Mar 2, 2018

@stoffu (LPSTR)&mszReaders is necessary. It's kind of C polymorphism hack, maybe a bit ugly but it works as is.

So I mod a bit your code proposition

@moneromooo-monero
Copy link
Collaborator

Please squash and fix the commit message and then it can go in.

@cslashm
Copy link
Contributor Author

cslashm commented Mar 4, 2018

squashed

The basic approach it to delegate all sensitive data (master key, secret
ephemeral key, key derivation, ....) and related operations to the device.
As device has low memory, it does not keep itself the values
(except for view/spend keys) but once computed there are encrypted (with AES
are equivalent) and return back to monero-wallet-cli. When they need to be
manipulated by the device, they are decrypted on receive.

Moreover, using the client for storing the value in encrypted form limits
the modification in the client code. Those values are transfered from one
C-structure to another one as previously.

The code modification has been done with the wishes to be open to any
other hardware wallet. To achieve that a C++ class hw::Device has been
introduced. Two initial implementations are provided: the "default", which
remaps all calls to initial Monero code, and  the "Ledger", which delegates
all calls to Ledger device.
Copy link
Contributor

@fluffypony fluffypony left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed

@fluffypony fluffypony merged commit e745c1e into monero-project:master Mar 4, 2018
fluffypony added a commit that referenced this pull request Mar 4, 2018
e745c1e Code modifications to integrate Ledger HW device into monero-wallet-cli. (cslashm)
@maloner
Copy link

maloner commented Mar 4, 2018

Will this support NANO S & Blue. I see there are references above to Blue which has more RAM

stoffu added a commit to stoffu/monero that referenced this pull request Mar 6, 2018
stoffu added a commit to stoffu/monero that referenced this pull request Mar 6, 2018
When monero-project#3303 was merged, a cyclic dependency chain was generated:

    libdevice <- libcncrypto <- libringct <- libdevice

This was because libdevice needs access to a set of basic crypto operations
implemented in libringct such as scalarmultBase(), while libringct also needs
access to abstracted crypto operations implemented in libdevice such as
ecdhEncode(). To untangle this cyclic dependency chain, this patch splits libringct
into libringct_basic and libringct, where the basic crypto ops previously in
libringct are moved into libringct_basic. The cyclic dependency is now resolved
thanks to this separation:

    libcncrypto <- libringct_basic <- libdevice <- libcryptonote_basic <- libringct

This eliminates the need for crypto_device.cpp and rctOps_device.cpp.

Also, many abstracted interfaces of hw::device such as encrypt_payment_id() and
get_subaddress_secret_key() were previously implemented in libcryptonote_basic
(cryptonote_format_utils.cpp) and were then called from hw::core::device_default,
which is odd because libdevice is supposed to be independent of libcryptonote_basic.
Therefore, those functions were moved to device_default.cpp.
stoffu added a commit to stoffu/monero that referenced this pull request Mar 6, 2018
stoffu added a commit to stoffu/monero that referenced this pull request Mar 14, 2018
stoffu added a commit to stoffu/monero that referenced this pull request Mar 14, 2018
When monero-project#3303 was merged, a cyclic dependency chain was generated:

    libdevice <- libcncrypto <- libringct <- libdevice

This was because libdevice needs access to a set of basic crypto operations
implemented in libringct such as scalarmultBase(), while libringct also needs
access to abstracted crypto operations implemented in libdevice such as
ecdhEncode(). To untangle this cyclic dependency chain, this patch splits libringct
into libringct_basic and libringct, where the basic crypto ops previously in
libringct are moved into libringct_basic. The cyclic dependency is now resolved
thanks to this separation:

    libcncrypto <- libringct_basic <- libdevice <- libcryptonote_basic <- libringct

This eliminates the need for crypto_device.cpp and rctOps_device.cpp.

Also, many abstracted interfaces of hw::device such as encrypt_payment_id() and
get_subaddress_secret_key() were previously implemented in libcryptonote_basic
(cryptonote_format_utils.cpp) and were then called from hw::core::device_default,
which is odd because libdevice is supposed to be independent of libcryptonote_basic.
Therefore, those functions were moved to device_default.cpp.
stoffu added a commit to stoffu/monero that referenced this pull request Mar 14, 2018
fluffypony added a commit that referenced this pull request Mar 14, 2018
4405e4f wallet2: check_tx_key() shouldn't require hardware encryption (stoffu)
7dfa5e9 chacha: call prehashed version explicitly as generate_chacha_key_prehashed hash: add prehashed version cn_slow_hash_prehashed slow-hash: let cn_slow_hash take 4th parameter for deciding prehashed or not slow-hash: add support for prehashed version for the other 3 platforms (stoffu)
b2d23b1 crypto: revert odd namespace changes made in #3303 (stoffu)
8705bea keypair::generate: always require hw::device to avoid possible mistake (stoffu)
27a196b device: untangle cyclic depenency (stoffu)
c9b38b4 device: made function prototypes consistent with pre-#3303 codebase (stoffu)
@cslashm cslashm deleted the Ledger-NanoS branch April 12, 2018 13:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.