Conversation
|
Hey @ethanfrey, short of fixing up all the signatures to return type genLedger struct {
mu sync.Mutex
lastErr error
}b) On encountering any error, instead of panicking, save that 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 Please let me know what you think. |
|
What if instead of exposing this thing as a crypto.PrivKey we abstract the Signer, and give it a 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 |
|
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. |
keys/cryptostore/generator.go
Outdated
| return GenEd25519, nil | ||
| case crypto.NameSecp256k1: | ||
| return GenSecp256k1, nil | ||
| case nano.NameLedger: |
There was a problem hiding this comment.
IMO this should NameLedgerEd25519
The ledger can support more key types in the future.
There was a problem hiding this comment.
Sounds good, done.
f147fe3 to
9a9b02b
Compare
|
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. |
| SigLength = 64 | ||
| ) | ||
|
|
||
| var separator = []byte{0, 0xCA, 0xFE, 0} |
There was a problem hiding this comment.
fine for now, but we should eventually remove this separator in favor of just having hardcoded slice offsets.
ebuchman
left a comment
There was a problem hiding this comment.
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 :)
keys/cryptostore/generator.go
Outdated
| // 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) |
There was a problem hiding this comment.
Do we want to support Ledger-Secp256k1? Eg. the fundraiser keys
There was a problem hiding this comment.
Cosmos app doesn't support secp256k1, but i updated all the names to clarify this is ed25519
There was a problem hiding this comment.
Ok - the fundraiser keys were secp256k1. So we need the app to support it.
There was a problem hiding this comment.
@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?
nano/keys.go
Outdated
| // AssertIsPrivKeyInner fulfils PrivKey Interface | ||
| func (pk *PrivKeyLedger) AssertIsPrivKeyInner() {} | ||
|
|
||
| // Bytes fulfils pk Interface - no data, just type info |
There was a problem hiding this comment.
what's this mean just type info?
nano/keys.go
Outdated
| } | ||
|
|
||
| // Sign calls the ledger and stores the pk for future use | ||
| func (pk *PrivKeyLedger) Sign(msg []byte) crypto.Signature { |
There was a problem hiding this comment.
Let's add method comment here XXX/TODO: it panics if there's an error
nano/keys.go
Outdated
| } | ||
|
|
||
| // Equals fulfils PrivKey Interface | ||
| // TODO: needs to be fixed |
There was a problem hiding this comment.
should probably be addressed!
There was a problem hiding this comment.
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[:]) |
There was a problem hiding this comment.
could we not do pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519) ?
There was a problem hiding this comment.
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) |
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()andpriv.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.