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