github.com/sberex/go-sberex@v1.8.2-0.20181113200658-ed96ac38f7d7/accounts/usbwallet/trezor.go (about)

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