github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/accounts/usbwallet/trezor.go (about)

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