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