Skip to content
This repository was archived by the owner on Jul 15, 2018. It is now read-only.

Nano Support#37

Merged
ethanfrey merged 28 commits intodevelopfrom
nano
Oct 24, 2017
Merged

Nano Support#37
ethanfrey merged 28 commits intodevelopfrom
nano

Conversation

@ethanfrey
Copy link
Contributor

All drivers working with alpha cosmos app. Can sign keys. Want review and feedback.

This code was integrated with the CLI in cosmos-sdk and can successfully sign basecoin transactions. There is still some work to do for proper key management on the nano (not just one static key per device session), but for proof of concept, the code works.

One big issue I have is the PubKey/PrivKey interface. Naturally, we assumed the methods like priv.PubKey() and priv.Sign(..) would never fail, as it was math. Now there is I/O involved, and we get errors if the ledger is disconnected or the cosmos app is not open. Unfortunately, there is no way to return errors in the current interface, so I have to panic on those errors :( Changing the interface would break lots of code, and is not undertaken lightly.

Can I get some feedback here on how to do this proper? The success case works fine, the error handling just sucks.

@odeke-em
Copy link
Contributor

Hey @ethanfrey, short of fixing up all the signatures to return (crypto.PrivKey, error),
here is a hack that I think could help:
a) instead of making genLegder a GenFunc, make it a struct that can save state i.e the last error
e.g

type genLedger struct {
   mu sync.Mutex
   lastErr error
}

b) On encountering any error, instead of panicking, save that lastErr e.g gl.lastErr = err then return normally
c) Make genLedger implement the LastErrorer LastError() error interface which evicts the last error on every read. This ensures that we capture the last encountered error only once, sort of as if it were a function call. Thus

func (gl *genLedger) LastError() error {
   gl.mu.Lock()
   var err error = gl.lastErr
   gl.lastErr = nil
   gl.mu.Unlock()
   return err
}

func (gl *genLedger) setLastErr(err error) {
   gl.mu.Lock()
   gl.lastErr = err
   gl.mu.Unlock()
}

func (gl *genLedger) Generate(secret []byte) crypto.PrivKey {
   key, err := nano.NewPrivKeyLedger()
   if err != nil {
       gl.setLastErr(err)
   }
   return key
}

type LastErrorer interface {
   LastError() error
}

and then for usage:

key := gen.Generate()
if le, ok := gen.(LastErrorer); ok {
   if err := le.LastError(); err != nil {
     // Handle the error here
   }
}

or you can make that a helper inside the code that needs it

func generateKey(gen Generator) (crypto.PrivKey, error) {
   key := gen.Generate()
   le, ok := gen.(LastErrorer)
   if !ok {
      return key, nil
   }
   err := le.LastError()
   return key, err
}

and then use it normally in any of the usages as

key, err := generateKey(gen)

which will prevent mortally breaking this repo. With the mentioned pattern, we can slowly refactor all our code and eventually make all usages return (crypto.PrivKey, error) eventually achieving the proper signature.

Please let me know what you think.

@ebuchman
Copy link
Contributor

What if instead of exposing this thing as a crypto.PrivKey we abstract the Signer, and give it a Sign( ...) (crypto.Signature, error). Would this solve the problem ? Then the Signer could have additional rules that might cause it to err too. The consumers can just check for errors on calling Sign. Everything else should work fine, and constructors should populate pubkeys and return errors if they can't.

For instance this is how the PrivValidator works in Tendermint, via a Signer interface with a Sign function. And here's the commit where I just added the error: tendermint/tendermint@90c0267

@ethanfrey
Copy link
Contributor Author

Thank you both for the ideas. I will take a deeper look at the code you both suggested on Monday. But happy to see some solutions.

return GenEd25519, nil
case crypto.NameSecp256k1:
return GenSecp256k1, nil
case nano.NameLedger:
Copy link
Contributor

Choose a reason for hiding this comment

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

IMO this should NameLedgerEd25519

The ledger can support more key types in the future.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good, done.

@ethanfrey
Copy link
Contributor Author

I fixed up the Generator signature to allow for errors when creating new keys in the key manager. I also extended PrivKeyFromBytes to allow a PrivKey to validate its correctness upon loading, as an optional check. Only done by nano by pinging the app, but ed25519 could make sure the priv/pub key parts match for example, as we serialize them both.

Now errors are returned, not panics, in the key manager upon creating a key on the ledger or signing with the ledger, if it is not plugged in with the cosmos app running.

@ethanfrey ethanfrey changed the title WIP: Nano Support Nano Support Oct 23, 2017
SigLength = 64
)

var separator = []byte{0, 0xCA, 0xFE, 0}
Copy link

Choose a reason for hiding this comment

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

fine for now, but we should eventually remove this separator in favor of just having hardcoded slice offsets.

Copy link

Choose a reason for hiding this comment

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

(just a heads-up)

Copy link
Contributor

@ebuchman ebuchman left a comment

Choose a reason for hiding this comment

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

Seems relatively straight forward. Haven't consulted the manual or tested it yet but otherwise looks good.

The ethanfrey/ledger library could use some comments :)

// GenSecp256k1 produces Secp256k1 private keys
GenSecp256k1 Generator = GenFunc(genSecp256)
// GenLedger used Ed25519 keys stored on nano ledger s with cosmos app
GenLedger Generator = GenFunc(genLedger)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to support Ledger-Secp256k1? Eg. the fundraiser keys

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Cosmos app doesn't support secp256k1, but i updated all the names to clarify this is ed25519

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok - the fundraiser keys were secp256k1. So we need the app to support it.

Copy link

Choose a reason for hiding this comment

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

@ebuchman we touched on this on slack briefly – do we have any need for the ledger to support secp256k1, or will it remain ed25519-only?

Copy link
Contributor

Choose a reason for hiding this comment

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

it would be good too.

nano/keys.go Outdated
// AssertIsPrivKeyInner fulfils PrivKey Interface
func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {}

// Bytes fulfils pk Interface - no data, just type info
Copy link
Contributor

Choose a reason for hiding this comment

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

what's this mean just type info?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed comment

nano/keys.go Outdated
}

// Sign calls the ledger and stores the pk for future use
func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's add method comment here XXX/TODO: it panics if there's an 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.

Added

nano/keys.go Outdated
}

// Equals fulfils PrivKey Interface
// TODO: needs to be fixed
Copy link
Contributor

Choose a reason for hiding this comment

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

should probably be addressed!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was before I stored the cached key, as it was regenerated every command. Now fixed it.

nano/keys.go Outdated
// Equals implements PubKey interface
func (pk PubKeyLedger) Equals(other crypto.PubKey) bool {
if ledger, ok := other.Unwrap().(PubKeyLedger); ok {
return bytes.Equal(pk.PubKeyEd25519[:], ledger.PubKeyEd25519[:])
Copy link
Contributor

Choose a reason for hiding this comment

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

could we not do pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519) ?

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. Except we need to add a Wrap() to make it compile. Replaced this logic.

nano/keys.go Outdated

packets := generateSignRequests(msg)
for _, pack := range packets {
resp, err = device.Exchange(pack, 100)
Copy link
Contributor

Choose a reason for hiding this comment

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

magic number

@ethanfrey ethanfrey merged commit 0a5b1d9 into develop Oct 24, 2017
@ethanfrey ethanfrey deleted the nano branch October 24, 2017 10:18
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants