github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/crypto/ledger_secp256k1.go (about) 1 package crypto 2 3 import ( 4 "fmt" 5 "os" 6 7 "github.com/btcsuite/btcd/btcec" 8 "github.com/pkg/errors" 9 10 tmcrypto "github.com/fibonacci-chain/fbc/libs/tendermint/crypto" 11 tmsecp256k1 "github.com/fibonacci-chain/fbc/libs/tendermint/crypto/secp256k1" 12 tmbtcec "github.com/tendermint/btcd/btcec" 13 14 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/crypto/keys/hd" 15 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 16 ) 17 18 var ( 19 // discoverLedger defines a function to be invoked at runtime for discovering 20 // a connected Ledger device. 21 discoverLedger discoverLedgerFn 22 ) 23 24 type ( 25 // discoverLedgerFn defines a Ledger discovery function that returns a 26 // connected device or an error upon failure. Its allows a method to avoid CGO 27 // dependencies when Ledger support is potentially not enabled. 28 discoverLedgerFn func() (LedgerSECP256K1, error) 29 30 // LedgerSECP256K1 reflects an interface a Ledger API must implement for SECP256K1 31 LedgerSECP256K1 interface { 32 Close() error 33 // Returns an uncompressed pubkey 34 GetPublicKeySECP256K1([]uint32) ([]byte, error) 35 // Returns a compressed pubkey and bech32 address (requires user confirmation) 36 GetAddressPubKeySECP256K1([]uint32, string) ([]byte, string, error) 37 // Signs a message (requires user confirmation) 38 SignSECP256K1([]uint32, []byte) ([]byte, error) 39 } 40 41 // PrivKeyLedgerSecp256k1 implements PrivKey, calling the ledger nano we 42 // cache the PubKey from the first call to use it later. 43 PrivKeyLedgerSecp256k1 struct { 44 // CachedPubKey should be private, but we want to encode it via 45 // go-amino so we can view the address later, even without having the 46 // ledger attached. 47 CachedPubKey tmcrypto.PubKey 48 Path hd.BIP44Params 49 } 50 ) 51 52 // NewPrivKeyLedgerSecp256k1Unsafe will generate a new key and store the public key for later use. 53 // 54 // This function is marked as unsafe as it will retrieve a pubkey without user verification. 55 // It can only be used to verify a pubkey but never to create new accounts/keys. In that case, 56 // please refer to NewPrivKeyLedgerSecp256k1 57 func NewPrivKeyLedgerSecp256k1Unsafe(path hd.BIP44Params) (tmcrypto.PrivKey, error) { 58 device, err := getLedgerDevice() 59 if err != nil { 60 return nil, err 61 } 62 defer warnIfErrors(device.Close) 63 64 pubKey, err := getPubKeyUnsafe(device, path) 65 if err != nil { 66 return nil, err 67 } 68 69 return PrivKeyLedgerSecp256k1{pubKey, path}, nil 70 } 71 72 // NewPrivKeyLedgerSecp256k1 will generate a new key and store the public key for later use. 73 // The request will require user confirmation and will show account and index in the device 74 func NewPrivKeyLedgerSecp256k1(path hd.BIP44Params, hrp string) (tmcrypto.PrivKey, string, error) { 75 device, err := getLedgerDevice() 76 if err != nil { 77 return nil, "", err 78 } 79 defer warnIfErrors(device.Close) 80 81 pubKey, addr, err := getPubKeyAddrSafe(device, path, hrp) 82 if err != nil { 83 return nil, "", err 84 } 85 86 return PrivKeyLedgerSecp256k1{pubKey, path}, addr, nil 87 } 88 89 // PubKey returns the cached public key. 90 func (pkl PrivKeyLedgerSecp256k1) PubKey() tmcrypto.PubKey { 91 return pkl.CachedPubKey 92 } 93 94 // Sign returns a secp256k1 signature for the corresponding message 95 func (pkl PrivKeyLedgerSecp256k1) Sign(message []byte) ([]byte, error) { 96 device, err := getLedgerDevice() 97 if err != nil { 98 return nil, err 99 } 100 defer warnIfErrors(device.Close) 101 102 return sign(device, pkl, message) 103 } 104 105 // LedgerShowAddress triggers a ledger device to show the corresponding address. 106 func LedgerShowAddress(path hd.BIP44Params, expectedPubKey tmcrypto.PubKey) error { 107 device, err := getLedgerDevice() 108 if err != nil { 109 return err 110 } 111 defer warnIfErrors(device.Close) 112 113 pubKey, err := getPubKeyUnsafe(device, path) 114 if err != nil { 115 return err 116 } 117 118 if pubKey != expectedPubKey { 119 return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones") 120 } 121 122 config := sdk.GetConfig() 123 pubKey2, _, err := getPubKeyAddrSafe(device, path, config.GetBech32AccountAddrPrefix()) 124 if err != nil { 125 return err 126 } 127 128 if pubKey2 != expectedPubKey { 129 return fmt.Errorf("the key's pubkey does not match with the one retrieved from Ledger. Check that the HD path and device are the correct ones") 130 } 131 132 return nil 133 } 134 135 // ValidateKey allows us to verify the sanity of a public key after loading it 136 // from disk. 137 func (pkl PrivKeyLedgerSecp256k1) ValidateKey() error { 138 device, err := getLedgerDevice() 139 if err != nil { 140 return err 141 } 142 defer warnIfErrors(device.Close) 143 144 return validateKey(device, pkl) 145 } 146 147 // AssertIsPrivKeyInner implements the PrivKey interface. It performs a no-op. 148 func (pkl *PrivKeyLedgerSecp256k1) AssertIsPrivKeyInner() {} 149 150 // Bytes implements the PrivKey interface. It stores the cached public key so 151 // we can verify the same key when we reconnect to a ledger. 152 func (pkl PrivKeyLedgerSecp256k1) Bytes() []byte { 153 return cdc.MustMarshalBinaryBare(pkl) 154 } 155 156 // Equals implements the PrivKey interface. It makes sure two private keys 157 // refer to the same public key. 158 func (pkl PrivKeyLedgerSecp256k1) Equals(other tmcrypto.PrivKey) bool { 159 if otherKey, ok := other.(PrivKeyLedgerSecp256k1); ok { 160 return pkl.CachedPubKey.Equals(otherKey.CachedPubKey) 161 } 162 return false 163 } 164 165 // warnIfErrors wraps a function and writes a warning to stderr. This is required 166 // to avoid ignoring errors when defer is used. Using defer may result in linter warnings. 167 func warnIfErrors(f func() error) { 168 if err := f(); err != nil { 169 _, _ = fmt.Fprint(os.Stderr, "received error when closing ledger connection", err) 170 } 171 } 172 173 func convertDERtoBER(signatureDER []byte) ([]byte, error) { 174 sigDER, err := btcec.ParseDERSignature(signatureDER, btcec.S256()) 175 if err != nil { 176 return nil, err 177 } 178 sigBER := tmbtcec.Signature{R: sigDER.R, S: sigDER.S} 179 return sigBER.Serialize(), nil 180 } 181 182 func getLedgerDevice() (LedgerSECP256K1, error) { 183 if discoverLedger == nil { 184 return nil, errors.New("no Ledger discovery function defined") 185 } 186 187 device, err := discoverLedger() 188 if err != nil { 189 return nil, errors.Wrap(err, "ledger nano S") 190 } 191 192 return device, nil 193 } 194 195 func validateKey(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1) error { 196 pub, err := getPubKeyUnsafe(device, pkl.Path) 197 if err != nil { 198 return err 199 } 200 201 // verify this matches cached address 202 if !pub.Equals(pkl.CachedPubKey) { 203 return fmt.Errorf("cached key does not match retrieved key") 204 } 205 206 return nil 207 } 208 209 // Sign calls the ledger and stores the PubKey for future use. 210 // 211 // Communication is checked on NewPrivKeyLedger and PrivKeyFromBytes, returning 212 // an error, so this should only trigger if the private key is held in memory 213 // for a while before use. 214 func sign(device LedgerSECP256K1, pkl PrivKeyLedgerSecp256k1, msg []byte) ([]byte, error) { 215 err := validateKey(device, pkl) 216 if err != nil { 217 return nil, err 218 } 219 220 sig, err := device.SignSECP256K1(pkl.Path.DerivationPath(), msg) 221 if err != nil { 222 return nil, err 223 } 224 225 return convertDERtoBER(sig) 226 } 227 228 // getPubKeyUnsafe reads the pubkey from a ledger device 229 // 230 // This function is marked as unsafe as it will retrieve a pubkey without user verification 231 // It can only be used to verify a pubkey but never to create new accounts/keys. In that case, 232 // please refer to getPubKeyAddrSafe 233 // 234 // since this involves IO, it may return an error, which is not exposed 235 // in the PubKey interface, so this function allows better error handling 236 func getPubKeyUnsafe(device LedgerSECP256K1, path hd.BIP44Params) (tmcrypto.PubKey, error) { 237 publicKey, err := device.GetPublicKeySECP256K1(path.DerivationPath()) 238 if err != nil { 239 return nil, fmt.Errorf("please open Cosmos app on the Ledger device - error: %v", err) 240 } 241 242 // re-serialize in the 33-byte compressed format 243 cmp, err := btcec.ParsePubKey(publicKey, btcec.S256()) 244 if err != nil { 245 return nil, fmt.Errorf("error parsing public key: %v", err) 246 } 247 248 var compressedPublicKey tmsecp256k1.PubKeySecp256k1 249 copy(compressedPublicKey[:], cmp.SerializeCompressed()) 250 251 return compressedPublicKey, nil 252 } 253 254 // getPubKeyAddr reads the pubkey and the address from a ledger device. 255 // This function is marked as Safe as it will require user confirmation and 256 // account and index will be shown in the device. 257 // 258 // Since this involves IO, it may return an error, which is not exposed 259 // in the PubKey interface, so this function allows better error handling. 260 func getPubKeyAddrSafe(device LedgerSECP256K1, path hd.BIP44Params, hrp string) (tmcrypto.PubKey, string, error) { 261 publicKey, addr, err := device.GetAddressPubKeySECP256K1(path.DerivationPath(), hrp) 262 if err != nil { 263 return nil, "", fmt.Errorf("address %s rejected", addr) 264 } 265 266 // re-serialize in the 33-byte compressed format 267 cmp, err := btcec.ParsePubKey(publicKey, btcec.S256()) 268 if err != nil { 269 return nil, "", fmt.Errorf("error parsing public key: %v", err) 270 } 271 272 var compressedPublicKey tmsecp256k1.PubKeySecp256k1 273 copy(compressedPublicKey[:], cmp.SerializeCompressed()) 274 275 return compressedPublicKey, addr, nil 276 }