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