trezor.go 14 KB

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