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