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