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