github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/usbwallet/ledger.go (about) 1 // This file is part of the go-sberex library. The go-sberex library is 2 // free software: you can redistribute it and/or modify it under the terms 3 // of the GNU Lesser General Public License as published by the Free 4 // Software Foundation, either version 3 of the License, or (at your option) 5 // any later version. 6 // 7 // The go-sberex library is distributed in the hope that it will be useful, 8 // but WITHOUT ANY WARRANTY; without even the implied warranty of 9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 10 // General Public License <http://www.gnu.org/licenses/> for more details. 11 12 // This file contains the implementation for interacting with the Ledger hardware 13 // wallets. The wire protocol spec can be found in the Ledger Blue GitHub repo: 14 // https://raw.githubusercontent.com/LedgerHQ/blue-app-eth/master/doc/ethapp.asc 15 16 package usbwallet 17 18 import ( 19 "encoding/binary" 20 "encoding/hex" 21 "errors" 22 "fmt" 23 "io" 24 "math/big" 25 26 "github.com/Sberex/go-sberex/accounts" 27 "github.com/Sberex/go-sberex/common" 28 "github.com/Sberex/go-sberex/common/hexutil" 29 "github.com/Sberex/go-sberex/core/types" 30 "github.com/Sberex/go-sberex/log" 31 "github.com/Sberex/go-sberex/rlp" 32 ) 33 34 // ledgerOpcode is an enumeration encoding the supported Ledger opcodes. 35 type ledgerOpcode byte 36 37 // ledgerParam1 is an enumeration encoding the supported Ledger parameters for 38 // specific opcodes. The same parameter values may be reused between opcodes. 39 type ledgerParam1 byte 40 41 // ledgerParam2 is an enumeration encoding the supported Ledger parameters for 42 // specific opcodes. The same parameter values may be reused between opcodes. 43 type ledgerParam2 byte 44 45 const ( 46 ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Sberex address for a given BIP 32 path 47 ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Sberex transaction after having the user validate the parameters 48 ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration 49 50 ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet 51 ledgerP1ConfirmFetchAddress ledgerParam1 = 0x01 // Require a user confirmation before returning the address 52 ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing 53 ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing 54 ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address 55 ledgerP2ReturnAddressChainCode ledgerParam2 = 0x01 // Require a user confirmation before returning the address 56 ) 57 58 // errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange 59 // if the device replies with a mismatching header. This usually means the device 60 // is in browser mode. 61 var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header") 62 63 // errLedgerInvalidVersionReply is the error message returned by a Ledger version retrieval 64 // when a response does arrive, but it does not contain the expected data. 65 var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply") 66 67 // ledgerDriver implements the communication with a Ledger hardware wallet. 68 type ledgerDriver struct { 69 device io.ReadWriter // USB device connection to communicate through 70 version [3]byte // Current version of the Ledger firmware (zero if app is offline) 71 browser bool // Flag whether the Ledger is in browser mode (reply channel mismatch) 72 failure error // Any failure that would make the device unusable 73 log log.Logger // Contextual logger to tag the ledger with its id 74 } 75 76 // newLedgerDriver creates a new instance of a Ledger USB protocol driver. 77 func newLedgerDriver(logger log.Logger) driver { 78 return &ledgerDriver{ 79 log: logger, 80 } 81 } 82 83 // Status implements usbwallet.driver, returning various states the Ledger can 84 // currently be in. 85 func (w *ledgerDriver) Status() (string, error) { 86 if w.failure != nil { 87 return fmt.Sprintf("Failed: %v", w.failure), w.failure 88 } 89 if w.browser { 90 return "Sberex app in browser mode", w.failure 91 } 92 if w.offline() { 93 return "Sberex app offline", w.failure 94 } 95 return fmt.Sprintf("Sberex app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure 96 } 97 98 // offline returns whether the wallet and the Sberex app is offline or not. 99 // 100 // The method assumes that the state lock is held! 101 func (w *ledgerDriver) offline() bool { 102 return w.version == [3]byte{0, 0, 0} 103 } 104 105 // Open implements usbwallet.driver, attempting to initialize the connection to the 106 // Ledger hardware wallet. The Ledger does not require a user passphrase, so that 107 // parameter is silently discarded. 108 func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error { 109 w.device, w.failure = device, nil 110 111 _, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath) 112 if err != nil { 113 // Sberex app is not running or in browser mode, nothing more to do, return 114 if err == errLedgerReplyInvalidHeader { 115 w.browser = true 116 } 117 return nil 118 } 119 // Try to resolve the Sberex app's version, will fail prior to v1.0.2 120 if w.version, err = w.ledgerVersion(); err != nil { 121 w.version = [3]byte{1, 0, 0} // Assume worst case, can't verify if v1.0.0 or v1.0.1 122 } 123 return nil 124 } 125 126 // Close implements usbwallet.driver, cleaning up and metadata maintained within 127 // the Ledger driver. 128 func (w *ledgerDriver) Close() error { 129 w.browser, w.version = false, [3]byte{} 130 return nil 131 } 132 133 // Heartbeat implements usbwallet.driver, performing a sanity check against the 134 // Ledger to see if it's still online. 135 func (w *ledgerDriver) Heartbeat() error { 136 if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply { 137 w.failure = err 138 return err 139 } 140 return nil 141 } 142 143 // Derive implements usbwallet.driver, sending a derivation request to the Ledger 144 // and returning the Sberex address located on that derivation path. 145 func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, error) { 146 return w.ledgerDerive(path) 147 } 148 149 // SignTx implements usbwallet.driver, sending the transaction to the Ledger and 150 // waiting for the user to confirm or deny the transaction. 151 // 152 // Note, if the version of the Sberex application running on the Ledger wallet is 153 // too old to sign EIP-155 transactions, but such is requested nonetheless, an error 154 // will be returned opposed to silently signing in Homestead mode. 155 func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { 156 // If the Sberex app doesn't run, abort 157 if w.offline() { 158 return common.Address{}, nil, accounts.ErrWalletClosed 159 } 160 // Ensure the wallet is capable of signing the given transaction 161 if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 { 162 return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) 163 } 164 // All infos gathered and metadata checks out, request signing 165 return w.ledgerSign(path, tx, chainID) 166 } 167 168 // ledgerVersion retrieves the current version of the Sberex wallet app running 169 // on the Ledger wallet. 170 // 171 // The version retrieval protocol is defined as follows: 172 // 173 // CLA | INS | P1 | P2 | Lc | Le 174 // ----+-----+----+----+----+--- 175 // E0 | 06 | 00 | 00 | 00 | 04 176 // 177 // With no input data, and the output data being: 178 // 179 // Description | Length 180 // ---------------------------------------------------+-------- 181 // Flags 01: arbitrary data signature enabled by user | 1 byte 182 // Application major version | 1 byte 183 // Application minor version | 1 byte 184 // Application patch version | 1 byte 185 func (w *ledgerDriver) ledgerVersion() ([3]byte, error) { 186 // Send the request and wait for the response 187 reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil) 188 if err != nil { 189 return [3]byte{}, err 190 } 191 if len(reply) != 4 { 192 return [3]byte{}, errLedgerInvalidVersionReply 193 } 194 // Cache the version for future reference 195 var version [3]byte 196 copy(version[:], reply[1:]) 197 return version, nil 198 } 199 200 // ledgerDerive retrieves the currently active Sberex address from a Ledger 201 // wallet at the specified derivation path. 202 // 203 // The address derivation protocol is defined as follows: 204 // 205 // CLA | INS | P1 | P2 | Lc | Le 206 // ----+-----+----+----+-----+--- 207 // E0 | 02 | 00 return address 208 // 01 display address and confirm before returning 209 // | 00: do not return the chain code 210 // | 01: return the chain code 211 // | var | 00 212 // 213 // Where the input data is: 214 // 215 // Description | Length 216 // -------------------------------------------------+-------- 217 // Number of BIP 32 derivations to perform (max 10) | 1 byte 218 // First derivation index (big endian) | 4 bytes 219 // ... | 4 bytes 220 // Last derivation index (big endian) | 4 bytes 221 // 222 // And the output data is: 223 // 224 // Description | Length 225 // ------------------------+------------------- 226 // Public Key length | 1 byte 227 // Uncompressed Public Key | arbitrary 228 // Sberex address length | 1 byte 229 // Sberex address | 40 bytes hex ascii 230 // Chain code if requested | 32 bytes 231 func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) { 232 // Flatten the derivation path into the Ledger request 233 path := make([]byte, 1+4*len(derivationPath)) 234 path[0] = byte(len(derivationPath)) 235 for i, component := range derivationPath { 236 binary.BigEndian.PutUint32(path[1+4*i:], component) 237 } 238 // Send the request and wait for the response 239 reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path) 240 if err != nil { 241 return common.Address{}, err 242 } 243 // Discard the public key, we don't need that for now 244 if len(reply) < 1 || len(reply) < 1+int(reply[0]) { 245 return common.Address{}, errors.New("reply lacks public key entry") 246 } 247 reply = reply[1+int(reply[0]):] 248 249 // Extract the Sberex hex address string 250 if len(reply) < 1 || len(reply) < 1+int(reply[0]) { 251 return common.Address{}, errors.New("reply lacks address entry") 252 } 253 hexstr := reply[1 : 1+int(reply[0])] 254 255 // Decode the hex sting into an Sberex address and return 256 var address common.Address 257 hex.Decode(address[:], hexstr) 258 return address, nil 259 } 260 261 // ledgerSign sends the transaction to the Ledger wallet, and waits for the user 262 // to confirm or deny the transaction. 263 // 264 // The transaction signing protocol is defined as follows: 265 // 266 // CLA | INS | P1 | P2 | Lc | Le 267 // ----+-----+----+----+-----+--- 268 // E0 | 04 | 00: first transaction data block 269 // 80: subsequent transaction data block 270 // | 00 | variable | variable 271 // 272 // Where the input for the first transaction block (first 255 bytes) is: 273 // 274 // Description | Length 275 // -------------------------------------------------+---------- 276 // Number of BIP 32 derivations to perform (max 10) | 1 byte 277 // First derivation index (big endian) | 4 bytes 278 // ... | 4 bytes 279 // Last derivation index (big endian) | 4 bytes 280 // RLP transaction chunk | arbitrary 281 // 282 // And the input for subsequent transaction blocks (first 255 bytes) are: 283 // 284 // Description | Length 285 // ----------------------+---------- 286 // RLP transaction chunk | arbitrary 287 // 288 // And the output data is: 289 // 290 // Description | Length 291 // ------------+--------- 292 // signature V | 1 byte 293 // signature R | 32 bytes 294 // signature S | 32 bytes 295 func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) { 296 // Flatten the derivation path into the Ledger request 297 path := make([]byte, 1+4*len(derivationPath)) 298 path[0] = byte(len(derivationPath)) 299 for i, component := range derivationPath { 300 binary.BigEndian.PutUint32(path[1+4*i:], component) 301 } 302 // Create the transaction RLP based on whether legacy or EIP155 signing was requeste 303 var ( 304 txrlp []byte 305 err error 306 ) 307 if chainID == nil { 308 if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data()}); err != nil { 309 return common.Address{}, nil, err 310 } 311 } else { 312 if txrlp, err = rlp.EncodeToBytes([]interface{}{tx.Nonce(), tx.GasPrice(), tx.Gas(), tx.To(), tx.Value(), tx.Data(), chainID, big.NewInt(0), big.NewInt(0)}); err != nil { 313 return common.Address{}, nil, err 314 } 315 } 316 payload := append(path, txrlp...) 317 318 // Send the request and wait for the response 319 var ( 320 op = ledgerP1InitTransactionData 321 reply []byte 322 ) 323 for len(payload) > 0 { 324 // Calculate the size of the next data chunk 325 chunk := 255 326 if chunk > len(payload) { 327 chunk = len(payload) 328 } 329 // Send the chunk over, ensuring it's processed correctly 330 reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) 331 if err != nil { 332 return common.Address{}, nil, err 333 } 334 // Shift the payload and ensure subsequent chunks are marked as such 335 payload = payload[chunk:] 336 op = ledgerP1ContTransactionData 337 } 338 // Extract the Sberex signature and do a sanity validation 339 if len(reply) != 65 { 340 return common.Address{}, nil, errors.New("reply lacks signature") 341 } 342 signature := append(reply[1:], reply[0]) 343 344 // Create the correct signer and signature transform based on the chain ID 345 var signer types.Signer 346 if chainID == nil { 347 signer = new(types.HomesteadSigner) 348 } else { 349 signer = types.NewEIP155Signer(chainID) 350 signature[64] = signature[64] - byte(chainID.Uint64()*2+35) 351 } 352 signed, err := tx.WithSignature(signer, signature) 353 if err != nil { 354 return common.Address{}, nil, err 355 } 356 sender, err := types.Sender(signer, signed) 357 if err != nil { 358 return common.Address{}, nil, err 359 } 360 return sender, signed, nil 361 } 362 363 // ledgerExchange performs a data exchange with the Ledger wallet, sending it a 364 // message and retrieving the response. 365 // 366 // The common transport header is defined as follows: 367 // 368 // Description | Length 369 // --------------------------------------+---------- 370 // Communication channel ID (big endian) | 2 bytes 371 // Command tag | 1 byte 372 // Packet sequence index (big endian) | 2 bytes 373 // Payload | arbitrary 374 // 375 // The Communication channel ID allows commands multiplexing over the same 376 // physical link. It is not used for the time being, and should be set to 0101 377 // to avoid compatibility issues with implementations ignoring a leading 00 byte. 378 // 379 // The Command tag describes the message content. Use TAG_APDU (0x05) for standard 380 // APDU payloads, or TAG_PING (0x02) for a simple link test. 381 // 382 // The Packet sequence index describes the current sequence for fragmented payloads. 383 // The first fragment index is 0x00. 384 // 385 // APDU Command payloads are encoded as follows: 386 // 387 // Description | Length 388 // ----------------------------------- 389 // APDU length (big endian) | 2 bytes 390 // APDU CLA | 1 byte 391 // APDU INS | 1 byte 392 // APDU P1 | 1 byte 393 // APDU P2 | 1 byte 394 // APDU length | 1 byte 395 // Optional APDU data | arbitrary 396 func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { 397 // Construct the message payload, possibly split into multiple chunks 398 apdu := make([]byte, 2, 7+len(data)) 399 400 binary.BigEndian.PutUint16(apdu, uint16(5+len(data))) 401 apdu = append(apdu, []byte{0xe0, byte(opcode), byte(p1), byte(p2), byte(len(data))}...) 402 apdu = append(apdu, data...) 403 404 // Stream all the chunks to the device 405 header := []byte{0x01, 0x01, 0x05, 0x00, 0x00} // Channel ID and command tag appended 406 chunk := make([]byte, 64) 407 space := len(chunk) - len(header) 408 409 for i := 0; len(apdu) > 0; i++ { 410 // Construct the new message to stream 411 chunk = append(chunk[:0], header...) 412 binary.BigEndian.PutUint16(chunk[3:], uint16(i)) 413 414 if len(apdu) > space { 415 chunk = append(chunk, apdu[:space]...) 416 apdu = apdu[space:] 417 } else { 418 chunk = append(chunk, apdu...) 419 apdu = nil 420 } 421 // Send over to the device 422 w.log.Trace("Data chunk sent to the Ledger", "chunk", hexutil.Bytes(chunk)) 423 if _, err := w.device.Write(chunk); err != nil { 424 return nil, err 425 } 426 } 427 // Stream the reply back from the wallet in 64 byte chunks 428 var reply []byte 429 chunk = chunk[:64] // Yeah, we surely have enough space 430 for { 431 // Read the next chunk from the Ledger wallet 432 if _, err := io.ReadFull(w.device, chunk); err != nil { 433 return nil, err 434 } 435 w.log.Trace("Data chunk received from the Ledger", "chunk", hexutil.Bytes(chunk)) 436 437 // Make sure the transport header matches 438 if chunk[0] != 0x01 || chunk[1] != 0x01 || chunk[2] != 0x05 { 439 return nil, errLedgerReplyInvalidHeader 440 } 441 // If it's the first chunk, retrieve the total message length 442 var payload []byte 443 444 if chunk[3] == 0x00 && chunk[4] == 0x00 { 445 reply = make([]byte, 0, int(binary.BigEndian.Uint16(chunk[5:7]))) 446 payload = chunk[7:] 447 } else { 448 payload = chunk[5:] 449 } 450 // Append to the reply and stop when filled up 451 if left := cap(reply) - len(reply); left > len(payload) { 452 reply = append(reply, payload...) 453 } else { 454 reply = append(reply, payload[:left]...) 455 break 456 } 457 } 458 return reply[:len(reply)-2], nil 459 }