github.com/aigarnetwork/aigar@v0.0.0-20191115204914-d59a6eb70f8e/accounts/usbwallet/trezor.go (about)

     1  //  Copyright 2018 The go-ethereum Authors
     2  //  Copyright 2019 The go-aigar Authors
     3  //  This file is part of the go-aigar library.
     4  //
     5  //  The go-aigar library is free software: you can redistribute it and/or modify
     6  //  it under the terms of the GNU Lesser General Public License as published by
     7  //  the Free Software Foundation, either version 3 of the License, or
     8  //  (at your option) any later version.
     9  //
    10  //  The go-aigar library is distributed in the hope that it will be useful,
    11  //  but WITHOUT ANY WARRANTY; without even the implied warranty of
    12  //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    13  //  GNU Lesser General Public License for more details.
    14  //
    15  //  You should have received a copy of the GNU Lesser General Public License
    16  //  along with the go-aigar library. If not, see <http://www.gnu.org/licenses/>.
    17  
    18  // This file contains the implementation for interacting with the Trezor hardware
    19  // wallets. The wire protocol spec can be found on the SatoshiLabs website:
    20  // https://doc.satoshilabs.com/trezor-tech/api-protobuf.html
    21  
    22  package usbwallet
    23  
    24  import (
    25  	"encoding/binary"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"math/big"
    30  
    31  	"github.com/AigarNetwork/aigar/accounts"
    32  	"github.com/AigarNetwork/aigar/accounts/usbwallet/trezor"
    33  	"github.com/AigarNetwork/aigar/common"
    34  	"github.com/AigarNetwork/aigar/common/hexutil"
    35  	"github.com/AigarNetwork/aigar/core/types"
    36  	"github.com/AigarNetwork/aigar/log"
    37  	"github.com/golang/protobuf/proto"
    38  )
    39  
    40  // ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
    41  // this case, the calling application should display a pinpad and send back the
    42  // encoded passphrase.
    43  var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
    44  
    45  // ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
    46  var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
    47  
    48  // errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
    49  // if the device replies with a mismatching header. This usually means the device
    50  // is in browser mode.
    51  var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
    52  
    53  // trezorDriver implements the communication with a Trezor hardware wallet.
    54  type trezorDriver struct {
    55  	device         io.ReadWriter // USB device connection to communicate through
    56  	version        [3]uint32     // Current version of the Trezor firmware
    57  	label          string        // Current textual label of the Trezor device
    58  	pinwait        bool          // Flags whether the device is waiting for PIN entry
    59  	passphrasewait bool          // Flags whether the device is waiting for passphrase entry
    60  	failure        error         // Any failure that would make the device unusable
    61  	log            log.Logger    // Contextual logger to tag the trezor with its id
    62  }
    63  
    64  // newTrezorDriver creates a new instance of a Trezor USB protocol driver.
    65  func newTrezorDriver(logger log.Logger) driver {
    66  	return &trezorDriver{
    67  		log: logger,
    68  	}
    69  }
    70  
    71  // Status implements accounts.Wallet, always whether the Trezor is opened, closed
    72  // or whether the Ethereum app was not started on it.
    73  func (w *trezorDriver) Status() (string, error) {
    74  	if w.failure != nil {
    75  		return fmt.Sprintf("Failed: %v", w.failure), w.failure
    76  	}
    77  	if w.device == nil {
    78  		return "Closed", w.failure
    79  	}
    80  	if w.pinwait {
    81  		return fmt.Sprintf("Trezor v%d.%d.%d '%s' waiting for PIN", w.version[0], w.version[1], w.version[2], w.label), w.failure
    82  	}
    83  	return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure
    84  }
    85  
    86  // Open implements usbwallet.driver, attempting to initialize the connection to
    87  // the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation:
    88  //  * The first phase is to initialize the connection and read the wallet's
    89  //    features. This phase is invoked if the provided passphrase is empty. The
    90  //    device will display the pinpad as a result and will return an appropriate
    91  //    error to notify the user that a second open phase is needed.
    92  //  * The second phase is to unlock access to the Trezor, which is done by the
    93  //    user actually providing a passphrase mapping a keyboard keypad to the pin
    94  //    number of the user (shuffled according to the pinpad displayed).
    95  //  * If needed the device will ask for passphrase which will require calling
    96  //    open again with the actual passphrase (3rd phase)
    97  func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
    98  	w.device, w.failure = device, nil
    99  
   100  	// If phase 1 is requested, init the connection and wait for user callback
   101  	if passphrase == "" && !w.passphrasewait {
   102  		// If we're already waiting for a PIN entry, insta-return
   103  		if w.pinwait {
   104  			return ErrTrezorPINNeeded
   105  		}
   106  		// Initialize a connection to the device
   107  		features := new(trezor.Features)
   108  		if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil {
   109  			return err
   110  		}
   111  		w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
   112  		w.label = features.GetLabel()
   113  
   114  		// Do a manual ping, forcing the device to ask for its PIN and Passphrase
   115  		askPin := true
   116  		askPassphrase := true
   117  		res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
   118  		if err != nil {
   119  			return err
   120  		}
   121  		// Only return the PIN request if the device wasn't unlocked until now
   122  		switch res {
   123  		case 0:
   124  			w.pinwait = true
   125  			return ErrTrezorPINNeeded
   126  		case 1:
   127  			w.pinwait = false
   128  			w.passphrasewait = true
   129  			return ErrTrezorPassphraseNeeded
   130  		case 2:
   131  			return nil // responded with trezor.Success
   132  		}
   133  	}
   134  	// Phase 2 requested with actual PIN entry
   135  	if w.pinwait {
   136  		w.pinwait = false
   137  		res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
   138  		if err != nil {
   139  			w.failure = err
   140  			return err
   141  		}
   142  		if res == 1 {
   143  			w.passphrasewait = true
   144  			return ErrTrezorPassphraseNeeded
   145  		}
   146  	} else if w.passphrasewait {
   147  		w.passphrasewait = false
   148  		if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
   149  			w.failure = err
   150  			return err
   151  		}
   152  	}
   153  
   154  	return nil
   155  }
   156  
   157  // Close implements usbwallet.driver, cleaning up and metadata maintained within
   158  // the Trezor driver.
   159  func (w *trezorDriver) Close() error {
   160  	w.version, w.label, w.pinwait = [3]uint32{}, "", false
   161  	return nil
   162  }
   163  
   164  // Heartbeat implements usbwallet.driver, performing a sanity check against the
   165  // Trezor to see if it's still online.
   166  func (w *trezorDriver) Heartbeat() error {
   167  	if _, err := w.trezorExchange(&trezor.Ping{}, new(trezor.Success)); err != nil {
   168  		w.failure = err
   169  		return err
   170  	}
   171  	return nil
   172  }
   173  
   174  // Derive implements usbwallet.driver, sending a derivation request to the Trezor
   175  // and returning the Ethereum address located on that derivation path.
   176  func (w *trezorDriver) Derive(path accounts.DerivationPath) (common.Address, error) {
   177  	return w.trezorDerive(path)
   178  }
   179  
   180  // SignTx implements usbwallet.driver, sending the transaction to the Trezor and
   181  // waiting for the user to confirm or deny the transaction.
   182  func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
   183  	if w.device == nil {
   184  		return common.Address{}, nil, accounts.ErrWalletClosed
   185  	}
   186  	return w.trezorSign(path, tx, chainID)
   187  }
   188  
   189  // trezorDerive sends a derivation request to the Trezor device and returns the
   190  // Ethereum address located on that path.
   191  func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, error) {
   192  	address := new(trezor.EthereumAddress)
   193  	if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
   194  		return common.Address{}, err
   195  	}
   196  	if addr := address.GetAddressBin(); len(addr) > 0 { // Older firmwares use binary fomats
   197  		return common.BytesToAddress(addr), nil
   198  	}
   199  	if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal fomats
   200  		return common.HexToAddress(addr), nil
   201  	}
   202  	return common.Address{}, errors.New("missing derived address")
   203  }
   204  
   205  // trezorSign sends the transaction to the Trezor wallet, and waits for the user
   206  // to confirm or deny the transaction.
   207  func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
   208  	// Create the transaction initiation message
   209  	data := tx.Data()
   210  	length := uint32(len(data))
   211  
   212  	request := &trezor.EthereumSignTx{
   213  		AddressN:   derivationPath,
   214  		Nonce:      new(big.Int).SetUint64(tx.Nonce()).Bytes(),
   215  		GasPrice:   tx.GasPrice().Bytes(),
   216  		GasLimit:   new(big.Int).SetUint64(tx.Gas()).Bytes(),
   217  		Value:      tx.Value().Bytes(),
   218  		DataLength: &length,
   219  	}
   220  	if to := tx.To(); to != nil {
   221  		// Non contract deploy, set recipient explicitly
   222  		hex := to.Hex()
   223  		request.ToHex = &hex     // Newer firmwares (old will ignore)
   224  		request.ToBin = (*to)[:] // Older firmwares (new will ignore)
   225  	}
   226  	if length > 1024 { // Send the data chunked if that was requested
   227  		request.DataInitialChunk, data = data[:1024], data[1024:]
   228  	} else {
   229  		request.DataInitialChunk, data = data, nil
   230  	}
   231  	if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?)
   232  		id := uint32(chainID.Int64())
   233  		request.ChainId = &id
   234  	}
   235  	// Send the initiation message and stream content until a signature is returned
   236  	response := new(trezor.EthereumTxRequest)
   237  	if _, err := w.trezorExchange(request, response); err != nil {
   238  		return common.Address{}, nil, err
   239  	}
   240  	for response.DataLength != nil && int(*response.DataLength) <= len(data) {
   241  		chunk := data[:*response.DataLength]
   242  		data = data[*response.DataLength:]
   243  
   244  		if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil {
   245  			return common.Address{}, nil, err
   246  		}
   247  	}
   248  	// Extract the Ethereum signature and do a sanity validation
   249  	if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 {
   250  		return common.Address{}, nil, errors.New("reply lacks signature")
   251  	}
   252  	signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
   253  
   254  	// Create the correct signer and signature transform based on the chain ID
   255  	var signer types.Signer
   256  	if chainID == nil {
   257  		signer = new(types.HomesteadSigner)
   258  	} else {
   259  		signer = types.NewEIP155Signer(chainID)
   260  		signature[64] -= byte(chainID.Uint64()*2 + 35)
   261  	}
   262  	// Inject the final signature into the transaction and sanity check the sender
   263  	signed, err := tx.WithSignature(signer, signature)
   264  	if err != nil {
   265  		return common.Address{}, nil, err
   266  	}
   267  	sender, err := types.Sender(signer, signed)
   268  	if err != nil {
   269  		return common.Address{}, nil, err
   270  	}
   271  	return sender, signed, nil
   272  }
   273  
   274  // trezorExchange performs a data exchange with the Trezor wallet, sending it a
   275  // message and retrieving the response. If multiple responses are possible, the
   276  // method will also return the index of the destination object used.
   277  func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Message) (int, error) {
   278  	// Construct the original message payload to chunk up
   279  	data, err := proto.Marshal(req)
   280  	if err != nil {
   281  		return 0, err
   282  	}
   283  	payload := make([]byte, 8+len(data))
   284  	copy(payload, []byte{0x23, 0x23})
   285  	binary.BigEndian.PutUint16(payload[2:], trezor.Type(req))
   286  	binary.BigEndian.PutUint32(payload[4:], uint32(len(data)))
   287  	copy(payload[8:], data)
   288  
   289  	// Stream all the chunks to the device
   290  	chunk := make([]byte, 64)
   291  	chunk[0] = 0x3f // Report ID magic number
   292  
   293  	for len(payload) > 0 {
   294  		// Construct the new message to stream, padding with zeroes if needed
   295  		if len(payload) > 63 {
   296  			copy(chunk[1:], payload[:63])
   297  			payload = payload[63:]
   298  		} else {
   299  			copy(chunk[1:], payload)
   300  			copy(chunk[1+len(payload):], make([]byte, 63-len(payload)))
   301  			payload = nil
   302  		}
   303  		// Send over to the device
   304  		w.log.Trace("Data chunk sent to the Trezor", "chunk", hexutil.Bytes(chunk))
   305  		if _, err := w.device.Write(chunk); err != nil {
   306  			return 0, err
   307  		}
   308  	}
   309  	// Stream the reply back from the wallet in 64 byte chunks
   310  	var (
   311  		kind  uint16
   312  		reply []byte
   313  	)
   314  	for {
   315  		// Read the next chunk from the Trezor wallet
   316  		if _, err := io.ReadFull(w.device, chunk); err != nil {
   317  			return 0, err
   318  		}
   319  		w.log.Trace("Data chunk received from the Trezor", "chunk", hexutil.Bytes(chunk))
   320  
   321  		// Make sure the transport header matches
   322  		if chunk[0] != 0x3f || (len(reply) == 0 && (chunk[1] != 0x23 || chunk[2] != 0x23)) {
   323  			return 0, errTrezorReplyInvalidHeader
   324  		}
   325  		// If it's the first chunk, retrieve the reply message type and total message length
   326  		var payload []byte
   327  
   328  		if len(reply) == 0 {
   329  			kind = binary.BigEndian.Uint16(chunk[3:5])
   330  			reply = make([]byte, 0, int(binary.BigEndian.Uint32(chunk[5:9])))
   331  			payload = chunk[9:]
   332  		} else {
   333  			payload = chunk[1:]
   334  		}
   335  		// Append to the reply and stop when filled up
   336  		if left := cap(reply) - len(reply); left > len(payload) {
   337  			reply = append(reply, payload...)
   338  		} else {
   339  			reply = append(reply, payload[:left]...)
   340  			break
   341  		}
   342  	}
   343  	// Try to parse the reply into the requested reply message
   344  	if kind == uint16(trezor.MessageType_MessageType_Failure) {
   345  		// Trezor returned a failure, extract and return the message
   346  		failure := new(trezor.Failure)
   347  		if err := proto.Unmarshal(reply, failure); err != nil {
   348  			return 0, err
   349  		}
   350  		return 0, errors.New("trezor: " + failure.GetMessage())
   351  	}
   352  	if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) {
   353  		// Trezor is waiting for user confirmation, ack and wait for the next message
   354  		return w.trezorExchange(&trezor.ButtonAck{}, results...)
   355  	}
   356  	for i, res := range results {
   357  		if trezor.Type(res) == kind {
   358  			return i, proto.Unmarshal(reply, res)
   359  		}
   360  	}
   361  	expected := make([]string, len(results))
   362  	for i, res := range results {
   363  		expected[i] = trezor.Name(trezor.Type(res))
   364  	}
   365  	return 0, fmt.Errorf("trezor: expected reply types %s, got %s", expected, trezor.Name(kind))
   366  }