forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 1
/
ledger_secp256k1.go
160 lines (130 loc) · 4.74 KB
/
ledger_secp256k1.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
package crypto
import (
"fmt"
"github.com/pkg/errors"
secp256k1 "github.com/btcsuite/btcd/btcec"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmsecp256k1 "github.com/tendermint/tendermint/crypto/secp256k1"
)
var (
// discoverLedger defines a function to be invoked at runtime for discovering
// a connected Ledger device.
discoverLedger discoverLedgerFn
)
type (
// discoverLedgerFn defines a Ledger discovery function that returns a
// connected device or an error upon failure. Its allows a method to avoid CGO
// dependencies when Ledger support is potentially not enabled.
discoverLedgerFn func() (LedgerSECP256K1, error)
// DerivationPath represents a Ledger derivation path.
DerivationPath []uint32
// LedgerSECP256K1 reflects an interface a Ledger API must implement for
// the SECP256K1 scheme.
LedgerSECP256K1 interface {
GetPublicKeySECP256K1([]uint32) ([]byte, error)
SignSECP256K1([]uint32, []byte) ([]byte, error)
}
// PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we
// cache the PubKey from the first call to use it later.
PrivKeyLedgerSecp256k1 struct {
// CachedPubKey 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 tmcrypto.PubKey
Path DerivationPath
ledger LedgerSECP256K1
}
)
// NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key
// for later use.
//
// CONTRACT: The ledger device, ledgerDevice, must be loaded and set prior to
// any creation of a PrivKeyLedgerSecp256k1.
func NewPrivKeyLedgerSecp256k1(path DerivationPath) (tmcrypto.PrivKey, error) {
if discoverLedger == nil {
return nil, errors.New("no Ledger discovery function defined")
}
device, err := discoverLedger()
if err != nil {
return nil, errors.Wrap(err, "failed to create PrivKeyLedgerSecp256k1")
}
pkl := &PrivKeyLedgerSecp256k1{Path: path, ledger: device}
pubKey, err := pkl.getPubKey()
if err != nil {
return nil, err
}
pkl.CachedPubKey = pubKey
return pkl, err
}
// PubKey returns the cached public key.
func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey {
return pkl.CachedPubKey
}
// ValidateKey allows us to verify the sanity of a public key after loading it
// from disk.
func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error {
// getPubKey will return an error if the ledger is not
pub, err := pkl.getPubKey()
if err != nil {
return err
}
// verify this matches cached address
if !pub.Equals(pkl.CachedPubKey) {
return fmt.Errorf("cached key does not match retrieved key")
}
return nil
}
// AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op.
func (pkl *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {}
// Bytes implements the PrivKey interface. It stores the cached public key so
// we can verify the same key when we reconnect to a ledger.
func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte {
return cdc.MustMarshalBinaryBare(pkl)
}
// Equals implements the PrivKey interface. It makes sure two private keys
// refer to the same public key.
func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool {
if ledger, ok := other.(*PrivKeyLedgerSecp256k1); ok {
return pkl.CachedPubKey.Equals(ledger.CachedPubKey)
}
return false
}
// Sign calls the ledger and stores the PubKey for future use.
//
// Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning
// an error, so this should only trigger if the private key is held in memory
// for a while before use.
func (pkl PrivKeyLedgerSecp256k1) Sign(msg []byte) ([]byte, error) {
sig, err := pkl.signLedgerSecp256k1(msg)
if err != nil {
return nil, err
}
return sig, nil
}
// getPubKey reads the pubkey 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 (pkl PrivKeyLedgerSecp256k1) getPubKey() (key tmcrypto.PubKey, err error) {
key, err = pkl.pubkeyLedgerSecp256k1()
if err != nil {
return key, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err)
}
return key, err
}
func (pkl PrivKeyLedgerSecp256k1) signLedgerSecp256k1(msg []byte) ([]byte, error) {
return pkl.ledger.SignSECP256K1(pkl.Path, msg)
}
func (pkl PrivKeyLedgerSecp256k1) pubkeyLedgerSecp256k1() (pub tmcrypto.PubKey, err error) {
key, err := pkl.ledger.GetPublicKeySECP256K1(pkl.Path)
if err != nil {
return nil, fmt.Errorf("error fetching public key: %v", err)
}
var pk tmsecp256k1.PubKeySecp256k1
// re-serialize in the 33-byte compressed format
cmp, err := secp256k1.ParsePubKey(key[:], secp256k1.S256())
if err != nil {
return nil, fmt.Errorf("error parsing public key: %v", err)
}
copy(pk[:], cmp.SerializeCompressed())
return pk, nil
}