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