package nano import ( "bytes" "encoding/hex" "github.com/pkg/errors" ledger "github.com/ethanfrey/ledger" crypto "github.com/tendermint/go-crypto" amino "github.com/tendermint/go-amino" ) //nolint const ( NameLedgerEd25519 = "ledger-ed25519" TypeLedgerEd25519 = 0x10 // Timeout is the number of seconds to wait for a response from the ledger // if eg. waiting for user confirmation on button push Timeout = 20 ) var device *ledger.Ledger // getLedger gets a copy of the device, and caches it func getLedger() (*ledger.Ledger, error) { var err error if device == nil { device, err = ledger.FindLedger() } return device, err } func signLedger(device *ledger.Ledger, msg []byte) (pub crypto.PubKey, sig crypto.Signature, err error) { var resp []byte packets := generateSignRequests(msg) for _, pack := range packets { resp, err = device.Exchange(pack, Timeout) if err != nil { return pub, sig, err } } // the last call is the result we want and needs to be parsed key, bsig, err := parseDigest(resp) if err != nil { return pub, sig, err } var b [32]byte copy(b[:], key) return PubKeyLedgerEd25519FromBytes(b), crypto.SignatureEd25519FromBytes(bsig), nil } // PrivKeyLedgerEd25519 implements PrivKey, calling the ledger nano // we cache the PubKey from the first call to use it later type PrivKeyLedgerEd25519 struct { // PubKey should be private, but we want to encode it via go-amino // so we can view the address later, even without having the ledger // attached CachedPubKey crypto.PubKey } // NewPrivKeyLedgerEd25519 will generate a new key and store the // public key for later use. func NewPrivKeyLedgerEd25519() (crypto.PrivKey, error) { var pk PrivKeyLedgerEd25519 // getPubKey will cache the pubkey for later use, // this allows us to return an error early if the ledger // is not plugged in _, err := pk.getPubKey() return pk.Wrap(), err } // ValidateKey allows us to verify the sanity of a key // after loading it from disk func (pk *PrivKeyLedgerEd25519) ValidateKey() error { // getPubKey will return an error if the ledger is not // properly set up... pub, err := pk.forceGetPubKey() if err != nil { return err } // verify this matches cached address if !pub.Equals(pk.CachedPubKey) { return errors.New("ledger doesn't match cached key") } return nil } // AssertIsPrivKeyInner fulfils PrivKey Interface func (pk *PrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} // Bytes fulfils PrivKey Interface - but it stores the cached pubkey so we can verify // the same key when we reconnect to a ledger func (pk *PrivKeyLedgerEd25519) Bytes() []byte { return amino.BinaryBytes(pk.Wrap()) } // Sign calls the ledger and stores the PubKey for future use // // XXX/TODO: panics if there is an error communicating with the ledger. // // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, // returning an error, so this should only trigger if the privkey is held // in memory for a while before use. func (pk *PrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { // oh, I wish there was better error handling dev, err := getLedger() if err != nil { panic(err) } pub, sig, err := signLedger(dev, msg) if err != nil { panic(err) } // if we have no pubkey yet, store it for future queries if pk.CachedPubKey.Empty() { pk.CachedPubKey = pub } else if !pk.CachedPubKey.Equals(pub) { panic("signed with a different key than stored") } return sig } // PubKey returns the stored PubKey // TODO: query the ledger if not there, once it is not volatile func (pk *PrivKeyLedgerEd25519) PubKey() crypto.PubKey { key, err := pk.getPubKey() if err != nil { panic(err) } return key } // getPubKey reads the pubkey from cache or from the ledger itself // since this involves IO, it may return an error, which is not exposed // in the PubKey interface, so this function allows better error handling func (pk *PrivKeyLedgerEd25519) getPubKey() (key crypto.PubKey, err error) { // if we have no pubkey, set it if pk.CachedPubKey.Empty() { pk.CachedPubKey, err = pk.forceGetPubKey() } return pk.CachedPubKey, err } // forceGetPubKey is like getPubKey but ignores any cached key // and ensures we get it from the ledger itself. func (pk *PrivKeyLedgerEd25519) forceGetPubKey() (key crypto.PubKey, err error) { dev, err := getLedger() if err != nil { return key, errors.New("Can't connect to ledger device") } key, _, err = signLedger(dev, []byte{0}) if err != nil { return key, errors.New("Please open cosmos app on the ledger") } return key, err } // Equals fulfils PrivKey Interface - makes sure both keys refer to the // same func (pk *PrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { if ledger, ok := other.Unwrap().(*PrivKeyLedgerEd25519); ok { return pk.CachedPubKey.Equals(ledger.CachedPubKey) } return false } // MockPrivKeyLedgerEd25519 behaves as the ledger, but stores a pre-packaged call-response // for use in test cases type MockPrivKeyLedgerEd25519 struct { Msg []byte Pub [KeyLength]byte Sig [SigLength]byte } // NewMockKey returns func NewMockKey(msg, pubkey, sig string) (pk MockPrivKeyLedgerEd25519) { var err error pk.Msg, err = hex.DecodeString(msg) if err != nil { panic(err) } bpk, err := hex.DecodeString(pubkey) if err != nil { panic(err) } bsig, err := hex.DecodeString(sig) if err != nil { panic(err) } copy(pk.Pub[:], bpk) copy(pk.Sig[:], bsig) return pk } var _ crypto.PrivKeyInner = MockPrivKeyLedgerEd25519{} // AssertIsPrivKeyInner fulfils PrivKey Interface func (pk MockPrivKeyLedgerEd25519) AssertIsPrivKeyInner() {} // Bytes fulfils PrivKey Interface - not supported func (pk MockPrivKeyLedgerEd25519) Bytes() []byte { return nil } // Sign returns a real SignatureLedger, if the msg matches what we expect func (pk MockPrivKeyLedgerEd25519) Sign(msg []byte) crypto.Signature { if !bytes.Equal(pk.Msg, msg) { panic("Mock key is for different msg") } return crypto.SignatureEd25519(pk.Sig).Wrap() } // PubKey returns a real PubKeyLedgerEd25519, that will verify this signature func (pk MockPrivKeyLedgerEd25519) PubKey() crypto.PubKey { return PubKeyLedgerEd25519FromBytes(pk.Pub) } // Equals compares that two Mocks have the same data func (pk MockPrivKeyLedgerEd25519) Equals(other crypto.PrivKey) bool { if mock, ok := other.Unwrap().(MockPrivKeyLedgerEd25519); ok { return bytes.Equal(mock.Pub[:], pk.Pub[:]) && bytes.Equal(mock.Sig[:], pk.Sig[:]) && bytes.Equal(mock.Msg, pk.Msg) } return false } //////////////////////////////////////////// // pubkey // PubKeyLedgerEd25519 works like a normal Ed25519 except a hash before the verify bytes type PubKeyLedgerEd25519 struct { crypto.PubKeyEd25519 } // PubKeyLedgerEd25519FromBytes creates a PubKey from the raw bytes func PubKeyLedgerEd25519FromBytes(key [32]byte) crypto.PubKey { return PubKeyLedgerEd25519{crypto.PubKeyEd25519(key)}.Wrap() } // Bytes fulfils pk Interface - no data, just type info func (pk PubKeyLedgerEd25519) Bytes() []byte { return amino.BinaryBytes(pk.Wrap()) } // VerifyBytes uses the normal Ed25519 algorithm but a sha512 hash beforehand func (pk PubKeyLedgerEd25519) VerifyBytes(msg []byte, sig crypto.Signature) bool { hmsg := hashMsg(msg) return pk.PubKeyEd25519.VerifyBytes(hmsg, sig) } // Equals implements PubKey interface func (pk PubKeyLedgerEd25519) Equals(other crypto.PubKey) bool { if ledger, ok := other.Unwrap().(PubKeyLedgerEd25519); ok { return pk.PubKeyEd25519.Equals(ledger.PubKeyEd25519.Wrap()) } return false } /*** registration with go-data ***/ func init() { crypto.PrivKeyMapper. RegisterImplementation(&PrivKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519). RegisterImplementation(MockPrivKeyLedgerEd25519{}, "mock-ledger", 0x11) crypto.PubKeyMapper. RegisterImplementation(PubKeyLedgerEd25519{}, NameLedgerEd25519, TypeLedgerEd25519) } // Wrap fulfils interface for PrivKey struct func (pk *PrivKeyLedgerEd25519) Wrap() crypto.PrivKey { return crypto.PrivKey{PrivKeyInner: pk} } // Wrap fulfils interface for PrivKey struct func (pk MockPrivKeyLedgerEd25519) Wrap() crypto.PrivKey { return crypto.PrivKey{PrivKeyInner: pk} } // Wrap fulfils interface for PubKey struct func (pk PubKeyLedgerEd25519) Wrap() crypto.PubKey { return crypto.PubKey{PubKeyInner: pk} }