mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-02-28 08:37:22 +00:00
Merge dc275944b2 into 406a852ec8
This commit is contained in:
commit
16f9b45af5
3 changed files with 903 additions and 598 deletions
|
|
@ -50,6 +50,8 @@ var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
|
|||
// is in browser mode.
|
||||
var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
|
||||
|
||||
const trezorMaxDataChunk = 1024
|
||||
|
||||
// trezorDriver implements the communication with a Trezor hardware wallet.
|
||||
type trezorDriver struct {
|
||||
device io.ReadWriter // USB device connection to communicate through
|
||||
|
|
@ -197,10 +199,7 @@ func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, er
|
|||
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
if addr := address.GetAddressBin(); len(addr) > 0 { // Older firmwares use binary formats
|
||||
return common.BytesToAddress(addr), nil
|
||||
}
|
||||
if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal formats
|
||||
if addr := address.GetAddress(); addr != "" {
|
||||
return common.HexToAddress(addr), nil
|
||||
}
|
||||
return common.Address{}, errors.New("missing derived address")
|
||||
|
|
@ -209,41 +208,24 @@ func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, er
|
|||
// trezorSign sends the transaction to the Trezor wallet, and waits for the user
|
||||
// to confirm or deny the transaction.
|
||||
func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
|
||||
// Create the transaction initiation message
|
||||
data := tx.Data()
|
||||
length := uint32(len(data))
|
||||
|
||||
request := &trezor.EthereumSignTx{
|
||||
AddressN: derivationPath,
|
||||
Nonce: new(big.Int).SetUint64(tx.Nonce()).Bytes(),
|
||||
GasPrice: tx.GasPrice().Bytes(),
|
||||
GasLimit: new(big.Int).SetUint64(tx.Gas()).Bytes(),
|
||||
Value: tx.Value().Bytes(),
|
||||
DataLength: &length,
|
||||
request, payload, err := w.buildSignRequest(derivationPath, tx, chainID)
|
||||
if err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
if to := tx.To(); to != nil {
|
||||
// Non contract deploy, set recipient explicitly
|
||||
hex := to.Hex()
|
||||
request.ToHex = &hex // Newer firmwares (old will ignore)
|
||||
request.ToBin = (*to)[:] // Older firmwares (new will ignore)
|
||||
}
|
||||
if length > 1024 { // Send the data chunked if that was requested
|
||||
request.DataInitialChunk, data = data[:1024], data[1024:]
|
||||
if len(payload) > trezorMaxDataChunk {
|
||||
setInitialChunk(request, payload[:trezorMaxDataChunk])
|
||||
payload = payload[trezorMaxDataChunk:]
|
||||
} else {
|
||||
request.DataInitialChunk, data = data, nil
|
||||
setInitialChunk(request, payload)
|
||||
payload = nil
|
||||
}
|
||||
if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?)
|
||||
id := uint32(chainID.Int64())
|
||||
request.ChainId = &id
|
||||
}
|
||||
// Send the initiation message and stream content until a signature is returned
|
||||
response := new(trezor.EthereumTxRequest)
|
||||
if _, err := w.trezorExchange(request, response); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
}
|
||||
for response.DataLength != nil && int(*response.DataLength) <= len(data) {
|
||||
chunk := data[:*response.DataLength]
|
||||
data = data[*response.DataLength:]
|
||||
for response.DataLength != nil && int(*response.DataLength) <= len(payload) {
|
||||
chunk := payload[:*response.DataLength]
|
||||
payload = payload[*response.DataLength:]
|
||||
|
||||
if _, err := w.trezorExchange(&trezor.EthereumTxAck{DataChunk: chunk}, response); err != nil {
|
||||
return common.Address{}, nil, err
|
||||
|
|
@ -261,15 +243,21 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
|||
|
||||
// Create the correct signer and signature transform based on the chain ID
|
||||
var signer types.Signer
|
||||
legacyTx := tx.Type() == types.LegacyTxType
|
||||
if chainID == nil {
|
||||
signer = new(types.HomesteadSigner)
|
||||
} else {
|
||||
// Trezor backend does not support typed transactions yet.
|
||||
signer = types.NewEIP155Signer(chainID)
|
||||
// if chainId is above (MaxUint32 - 36) / 2 then the final v values is returned
|
||||
// directly. Otherwise, the returned value is 35 + chainid * 2.
|
||||
if signature[64] > 1 && int(chainID.Int64()) <= (math.MaxUint32-36)/2 {
|
||||
signature[64] -= byte(chainID.Uint64()*2 + 35)
|
||||
signer = types.LatestSignerForChainID(chainID)
|
||||
// Legacy (EIP-155) transactions still return the final ECDSA V value.
|
||||
if legacyTx && signature[64] > 1 {
|
||||
if !chainID.IsUint64() {
|
||||
return common.Address{}, nil, errors.New("chain id overflows uint64")
|
||||
}
|
||||
sigAdj := chainID.Uint64()*2 + 35
|
||||
if sigAdj > math.MaxUint8 {
|
||||
return common.Address{}, nil, errors.New("chain id is too large for trezor response")
|
||||
}
|
||||
signature[64] -= byte(sigAdj)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -285,6 +273,126 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
|
|||
return sender, signed, nil
|
||||
}
|
||||
|
||||
func (w *trezorDriver) buildSignRequest(derivationPath []uint32, tx *types.Transaction, chainID *big.Int) (proto.Message, []byte, error) {
|
||||
payload := tx.Data()
|
||||
length := uint32(len(payload))
|
||||
switch tx.Type() {
|
||||
case types.LegacyTxType:
|
||||
req := &trezor.EthereumSignTx{
|
||||
AddressN: derivationPath,
|
||||
Nonce: encodeUint64(tx.Nonce()),
|
||||
GasPrice: bigIntBytes(tx.GasPrice()),
|
||||
GasLimit: encodeUint64(tx.Gas()),
|
||||
Value: bigIntBytes(tx.Value()),
|
||||
DataLength: &length,
|
||||
}
|
||||
if chainID != nil {
|
||||
if !chainID.IsUint64() {
|
||||
return nil, nil, errors.New("chain id overflows trezor message")
|
||||
}
|
||||
id := chainID.Uint64()
|
||||
req.ChainId = &id
|
||||
}
|
||||
if to := tx.To(); to != nil {
|
||||
addr := to.Hex()
|
||||
req.To = &addr
|
||||
}
|
||||
return req, payload, nil
|
||||
case types.AccessListTxType, types.DynamicFeeTxType, types.BlobTxType:
|
||||
if chainID == nil {
|
||||
return nil, nil, errors.New("typed transactions require a chain id")
|
||||
}
|
||||
if !chainID.IsUint64() {
|
||||
return nil, nil, errors.New("chain id overflows trezor message")
|
||||
}
|
||||
id := chainID.Uint64()
|
||||
req := &trezor.EthereumSignTxEIP1559{
|
||||
AddressN: derivationPath,
|
||||
Nonce: encodeUint64(tx.Nonce()),
|
||||
GasLimit: encodeUint64(tx.Gas()),
|
||||
Value: bigIntBytes(tx.Value()),
|
||||
DataLength: &length,
|
||||
ChainId: &id,
|
||||
AccessList: convertAccessListEIP1559(tx.AccessList()),
|
||||
}
|
||||
if to := tx.To(); to != nil {
|
||||
addr := to.Hex()
|
||||
req.To = &addr
|
||||
}
|
||||
switch tx.Type() {
|
||||
case types.AccessListTxType:
|
||||
price := bigIntBytes(tx.GasPrice())
|
||||
req.MaxGasFee = price
|
||||
req.MaxPriorityFee = price
|
||||
case types.DynamicFeeTxType:
|
||||
req.MaxGasFee = bigIntBytes(tx.GasFeeCap())
|
||||
req.MaxPriorityFee = bigIntBytes(tx.GasTipCap())
|
||||
case types.BlobTxType:
|
||||
req.MaxGasFee = bigIntBytes(tx.GasFeeCap())
|
||||
req.MaxPriorityFee = bigIntBytes(tx.GasTipCap())
|
||||
req.MaxFeePerBlobGas = bigIntBytes(tx.BlobGasFeeCap())
|
||||
req.BlobVersionedHashes = convertBlobHashes(tx.BlobHashes())
|
||||
}
|
||||
return req, payload, nil
|
||||
default:
|
||||
return nil, nil, fmt.Errorf("unsupported transaction type %d", tx.Type())
|
||||
}
|
||||
}
|
||||
|
||||
func encodeUint64(value uint64) []byte {
|
||||
if value == 0 {
|
||||
return nil
|
||||
}
|
||||
return new(big.Int).SetUint64(value).Bytes()
|
||||
}
|
||||
|
||||
func bigIntBytes(v *big.Int) []byte {
|
||||
if v == nil || v.Sign() == 0 {
|
||||
return nil
|
||||
}
|
||||
return new(big.Int).Set(v).Bytes()
|
||||
}
|
||||
|
||||
func convertAccessListEIP1559(list types.AccessList) []*trezor.EthereumSignTxEIP1559_EthereumAccessList {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([]*trezor.EthereumSignTxEIP1559_EthereumAccessList, len(list))
|
||||
for i, entry := range list {
|
||||
addr := entry.Address.Hex()
|
||||
addrCopy := addr
|
||||
keys := make([][]byte, len(entry.StorageKeys))
|
||||
for j, key := range entry.StorageKeys {
|
||||
keys[j] = append([]byte{}, key[:]...)
|
||||
}
|
||||
out[i] = &trezor.EthereumSignTxEIP1559_EthereumAccessList{
|
||||
Address: &addrCopy,
|
||||
StorageKeys: keys,
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func convertBlobHashes(hashes []common.Hash) [][]byte {
|
||||
if len(hashes) == 0 {
|
||||
return nil
|
||||
}
|
||||
out := make([][]byte, len(hashes))
|
||||
for i, h := range hashes {
|
||||
out[i] = append([]byte{}, h[:]...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func setInitialChunk(msg proto.Message, chunk []byte) {
|
||||
switch req := msg.(type) {
|
||||
case *trezor.EthereumSignTx:
|
||||
req.DataInitialChunk = chunk
|
||||
case *trezor.EthereumSignTxEIP1559:
|
||||
req.DataInitialChunk = chunk
|
||||
}
|
||||
}
|
||||
|
||||
// trezorExchange performs a data exchange with the Trezor wallet, sending it a
|
||||
// message and retrieving the response. If multiple responses are possible, the
|
||||
// method will also return the index of the destination object used.
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,18 +1,12 @@
|
|||
// This file originates from the SatoshiLabs Trezor `common` repository at:
|
||||
// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto
|
||||
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9.
|
||||
|
||||
syntax = "proto2";
|
||||
package hw.trezor.messages.ethereum;
|
||||
|
||||
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor";
|
||||
|
||||
// Sugar for easier handling in Java
|
||||
option java_package = "com.satoshilabs.trezor.lib.protobuf";
|
||||
option java_outer_classname = "TrezorMessageEthereum";
|
||||
|
||||
import "messages-common.proto";
|
||||
|
||||
import "options.proto";
|
||||
|
||||
/**
|
||||
* Request: Ask device for public key corresponding to address_n path
|
||||
|
|
@ -21,8 +15,8 @@ import "messages-common.proto";
|
|||
* @next Failure
|
||||
*/
|
||||
message EthereumGetPublicKey {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -30,8 +24,8 @@ message EthereumGetPublicKey {
|
|||
* @end
|
||||
*/
|
||||
message EthereumPublicKey {
|
||||
optional hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node
|
||||
optional string xpub = 2; // serialized form of public node
|
||||
required hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node
|
||||
required string xpub = 2; // serialized form of public node
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -41,8 +35,10 @@ message EthereumPublicKey {
|
|||
* @next Failure
|
||||
*/
|
||||
message EthereumGetAddress {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bool show_display = 2; // optionally show on display before sending the result
|
||||
optional bytes encoded_network = 3; // encoded Ethereum network, see external-definitions.md for details
|
||||
optional bool chunkify = 4; // display the address in chunks of 4 characters
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -50,30 +46,63 @@ message EthereumGetAddress {
|
|||
* @end
|
||||
*/
|
||||
message EthereumAddress {
|
||||
optional bytes addressBin = 1; // Ethereum address as 20 bytes (legacy firmwares)
|
||||
optional string addressHex = 2; // Ethereum address as hex string (newer firmwares)
|
||||
optional bytes _old_address = 1 [deprecated=true]; // trezor <1.8.0, <2.1.0 - raw bytes of Ethereum address
|
||||
optional string address = 2; // Ethereum address as hex-encoded string
|
||||
optional bytes mac = 3; // Address authentication code
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign transaction
|
||||
* All fields are optional from the protocol's point of view. Each field defaults to value `0` if missing.
|
||||
* gas_price, gas_limit and chain_id must be provided and non-zero.
|
||||
* All other fields are optional and default to value `0` if missing.
|
||||
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||
* @start
|
||||
* @next EthereumTxRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignTx {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bytes nonce = 2; // <=256 bit unsigned big endian
|
||||
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes gas_limit = 4; // <=256 bit unsigned big endian
|
||||
optional bytes toBin = 5; // recipient address (20 bytes, legacy firmware)
|
||||
optional string toHex = 11; // recipient address (hex string, newer firmware)
|
||||
optional bytes value = 6; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes)
|
||||
optional uint32 data_length = 8; // Length of transaction payload
|
||||
optional uint32 chain_id = 9; // Chain Id for EIP 155
|
||||
optional uint32 tx_type = 10; // (only for Wanchain)
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bytes nonce = 2 [default='']; // <=256 bit unsigned big endian
|
||||
required bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
|
||||
required bytes gas_limit = 4; // <=256 bit unsigned big endian
|
||||
optional string to = 11 [default='']; // recipient address
|
||||
optional bytes value = 6 [default='']; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes data_initial_chunk = 7 [default='']; // The initial data chunk (<= 1024 bytes)
|
||||
optional uint32 data_length = 8 [default=0]; // Length of transaction payload
|
||||
required uint64 chain_id = 9; // Chain Id for EIP 155
|
||||
optional uint32 tx_type = 10; // Used for Wanchain
|
||||
optional EthereumDefinitions definitions = 12; // network and/or token definitions for tx
|
||||
optional bool chunkify = 13; // display the address in chunks of 4 characters
|
||||
optional common.PaymentRequest payment_req = 14 [(experimental_field)=true]; // SLIP-24 payment request
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign EIP1559 transaction
|
||||
* Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
|
||||
* @start
|
||||
* @next EthereumTxRequest
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignTxEIP1559 {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes nonce = 2; // <=256 bit unsigned big endian
|
||||
required bytes max_gas_fee = 3; // <=256 bit unsigned big endian (in wei)
|
||||
required bytes max_priority_fee = 4; // <=256 bit unsigned big endian (in wei)
|
||||
required bytes gas_limit = 5; // <=256 bit unsigned big endian
|
||||
optional string to = 6 [default='']; // recipient address
|
||||
required bytes value = 7; // <=256 bit unsigned big endian (in wei)
|
||||
optional bytes data_initial_chunk = 8 [default='']; // The initial data chunk (<= 1024 bytes)
|
||||
required uint32 data_length = 9; // Length of transaction payload
|
||||
required uint64 chain_id = 10; // Chain Id for EIP 155
|
||||
repeated EthereumAccessList access_list = 11; // Access List
|
||||
optional EthereumDefinitions definitions = 12; // network and/or token definitions for tx
|
||||
optional bool chunkify = 13; // display the address in chunks of 4 characters
|
||||
optional common.PaymentRequest payment_req = 14 [(experimental_field)=true]; // SLIP-24 payment request
|
||||
|
||||
message EthereumAccessList {
|
||||
required string address = 1;
|
||||
repeated bytes storage_keys = 2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -95,7 +124,7 @@ message EthereumTxRequest {
|
|||
* @next EthereumTxRequest
|
||||
*/
|
||||
message EthereumTxAck {
|
||||
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
||||
required bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -105,8 +134,10 @@ message EthereumTxAck {
|
|||
* @next Failure
|
||||
*/
|
||||
message EthereumSignMessage {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
optional bytes message = 2; // message to be signed
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes message = 2; // message to be signed
|
||||
optional bytes encoded_network = 3; // encoded Ethereum network, see external-definitions.md for details
|
||||
optional bool chunkify = 4; // display the address in chunks of 4 characters
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -114,9 +145,8 @@ message EthereumSignMessage {
|
|||
* @end
|
||||
*/
|
||||
message EthereumMessageSignature {
|
||||
optional bytes addressBin = 1; // address used to sign the message (20 bytes, legacy firmware)
|
||||
optional bytes signature = 2; // signature of the message
|
||||
optional string addressHex = 3; // address used to sign the message (hex string, newer firmware)
|
||||
required bytes signature = 2; // signature of the message
|
||||
required string address = 3; // address used to sign the message
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -126,8 +156,39 @@ message EthereumMessageSignature {
|
|||
* @next Failure
|
||||
*/
|
||||
message EthereumVerifyMessage {
|
||||
optional bytes addressBin = 1; // address to verify (20 bytes, legacy firmware)
|
||||
optional bytes signature = 2; // signature to verify
|
||||
optional bytes message = 3; // message to verify
|
||||
optional string addressHex = 4; // address to verify (hex string, newer firmware)
|
||||
required bytes signature = 2; // signature to verify
|
||||
required bytes message = 3; // message to verify
|
||||
required string address = 4; // address to verify
|
||||
optional bool chunkify = 5; // display the address in chunks of 4 characters
|
||||
}
|
||||
|
||||
/**
|
||||
* Request: Ask device to sign hash of typed data
|
||||
* @start
|
||||
* @next EthereumTypedDataSignature
|
||||
* @next Failure
|
||||
*/
|
||||
message EthereumSignTypedHash {
|
||||
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
|
||||
required bytes domain_separator_hash = 2; // Hash of domainSeparator of typed data to be signed
|
||||
optional bytes message_hash = 3; // Hash of the data of typed data to be signed (empty if domain-only data)
|
||||
optional bytes encoded_network = 4; // encoded Ethereum network, see external-definitions.md for details
|
||||
}
|
||||
|
||||
/**
|
||||
* Response: Signed typed data
|
||||
* @end
|
||||
*/
|
||||
message EthereumTypedDataSignature {
|
||||
required bytes signature = 1; // signature of the typed data
|
||||
required string address = 2; // address used to sign the typed data
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains an encoded network and/or token definition. See external-definitions.md for details.
|
||||
* @embed
|
||||
*/
|
||||
message EthereumDefinitions {
|
||||
optional bytes encoded_network = 1; // encoded ethereum network
|
||||
optional bytes encoded_token = 2; // encoded ethereum token
|
||||
}
|
||||
Loading…
Reference in a new issue