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