Add support for signing EIP-712 typed data for both Ledger and Trezor devices

This commit is contained in:
Michael de Hoog 2025-09-26 22:14:04 +10:00
parent b1eb33ce8b
commit bfd5914a1d
No known key found for this signature in database
GPG key ID: 8ADF8244AA888446
20 changed files with 8093 additions and 3697 deletions

View file

@ -0,0 +1,106 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package usbwallet
import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
)
type dataType byte
const (
CustomType dataType = iota
IntType
UintType
AddressType
BoolType
StringType
FixedBytesType
BytesType
)
var nameToType = map[string]dataType{
"int": IntType,
"uint": UintType,
"address": AddressType,
"bool": BoolType,
"string": StringType,
"bytes": BytesType,
}
func parseType(data apitypes.TypedData, field apitypes.Type) (dt dataType, name string, byteLength int, arrayLevels []*int, err error) {
name = strings.TrimSpace(field.Type)
arrayLengths := regexp.MustCompile(`\[(\d*)]`).FindAllStringSubmatch(name, -1)
if len(arrayLengths) > 0 {
arrayLevels = make([]*int, len(arrayLengths))
for i, arrayLength := range arrayLengths {
if len(arrayLength[1]) == 0 {
arrayLevels[i] = nil // nil means dynamic length
} else {
length, _ := strconv.Atoi(arrayLength[1]) // guaranteed to be a digit, ignore error
arrayLevels[i] = &length
}
}
name = name[0:strings.Index(name, "[")]
}
if data.Types[name] != nil {
dt = CustomType
return
}
matches := regexp.MustCompile(`^(.+?)(\d*)$`).FindStringSubmatch(name)
name = matches[1]
lengthStr := matches[2]
var ok bool
if dt, ok = nameToType[name]; !ok {
err = fmt.Errorf("unknown type: %s", field.Type)
return
}
byteLength, _ = strconv.Atoi(lengthStr)
if dt == UintType || dt == IntType {
if lengthStr == "" {
byteLength = 32
} else if byteLength%8 != 0 {
err = fmt.Errorf("invalid length for %s: %s", field.Type, lengthStr)
return
} else {
byteLength /= 8
}
} else if lengthStr != "" {
if dt == BytesType {
dt = FixedBytesType
} else {
err = fmt.Errorf("invalid type: %s", field.Type)
return
}
} else if dt == AddressType {
byteLength = 20 // address is always 20 bytes
}
if lengthStr != "" && (byteLength < 1 || byteLength > 32) {
err = fmt.Errorf("invalid length for %s: %s", field.Type, lengthStr)
return
}
return
}

View file

@ -26,7 +26,7 @@ import (
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/event" "github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/karalabe/hid" "github.com/karalabe/usb"
) )
// LedgerScheme is the protocol scheme prefixing account and wallet URLs. // LedgerScheme is the protocol scheme prefixing account and wallet URLs.
@ -105,7 +105,7 @@ func NewTrezorHubWithWebUSB() (*Hub, error) {
// newHub creates a new hardware wallet manager for generic USB devices. // newHub creates a new hardware wallet manager for generic USB devices.
func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) { func newHub(scheme string, vendorID uint16, productIDs []uint16, usageID uint16, endpointID int, makeDriver func(log.Logger) driver) (*Hub, error) {
if !hid.Supported() { if !usb.Supported() {
return nil, errors.New("unsupported platform") return nil, errors.New("unsupported platform")
} }
hub := &Hub{ hub := &Hub{
@ -151,7 +151,7 @@ func (hub *Hub) refreshWallets() {
return return
} }
// Retrieve the current list of USB wallet devices // Retrieve the current list of USB wallet devices
var devices []hid.DeviceInfo var devices []usb.DeviceInfo
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {
// hidapi on Linux opens the device during enumeration to retrieve some infos, // hidapi on Linux opens the device during enumeration to retrieve some infos,
@ -166,7 +166,7 @@ func (hub *Hub) refreshWallets() {
return return
} }
} }
infos, err := hid.Enumerate(hub.vendorID, 0) infos, err := usb.Enumerate(hub.vendorID, 0)
if err != nil { if err != nil {
failcount := hub.enumFails.Add(1) failcount := hub.enumFails.Add(1)
if runtime.GOOS == "linux" { if runtime.GOOS == "linux" {

View file

@ -27,14 +27,18 @@ import (
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
"strconv"
"strings"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
) )
// ledgerOpcode is an enumeration encoding the supported Ledger opcodes. // ledgerOpcode is an enumeration encoding the supported Ledger opcodes.
@ -48,21 +52,60 @@ type ledgerParam1 byte
// specific opcodes. The same parameter values may be reused between opcodes. // specific opcodes. The same parameter values may be reused between opcodes.
type ledgerParam2 byte type ledgerParam2 byte
// ledgerStatus is an enumeration encoding ledger status words.
type ledgerStatus uint16
const ( const (
ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path ledgerOpRetrieveAddress ledgerOpcode = 0x02 // Returns the public key and Ethereum address for a given BIP 32 path
ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters ledgerOpSignTransaction ledgerOpcode = 0x04 // Signs an Ethereum transaction after having the user validate the parameters
ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration ledgerOpGetConfiguration ledgerOpcode = 0x06 // Returns specific wallet application configuration
ledgerOpSignPersonalMessage ledgerOpcode = 0x08 // Signs a personal message following the EIP 712 specification
ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification ledgerOpSignTypedMessage ledgerOpcode = 0x0c // Signs an Ethereum message following the EIP 712 specification
ledgerOpEip712SendStructDef ledgerOpcode = 0x1a // Sends EIP-712 struct types to the ledger
ledgerOpEip712SendStructImpl ledgerOpcode = 0x1c // Sends EIP-712 struct values to the ledger
ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet ledgerP1DirectlyFetchAddress ledgerParam1 = 0x00 // Return address directly from the wallet
ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data ledgerP1InitTypedMessageData ledgerParam1 = 0x00 // First chunk of Typed Message data
ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing ledgerP1InitTransactionData ledgerParam1 = 0x00 // First transaction data block for signing
ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing ledgerP1ContTransactionData ledgerParam1 = 0x80 // Subsequent transaction data block for signing
ledgerP1CompleteSend ledgerParam1 = 0x00 // Complete the value (in one go, or after partial)
ledgerP1PartialSend ledgerParam1 = 0x01 // Send partial data
ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address ledgerP2DiscardAddressChainCode ledgerParam2 = 0x00 // Do not return the chain code along with the address
ledgerP2ProcessAndStartFlow ledgerParam2 = 0x00 // Process and start transaction signing flow
ledgerP2V0Implementation ledgerParam2 = 0x00 // EIP-712 V0 implementation (hashes only)
ledgerP2StructName ledgerParam2 = 0x00 // Send EIP-712 struct name
ledgerP2RootStruct ledgerParam2 = 0x00 // Send EIP-712 root struct
ledgerP2Array ledgerParam2 = 0x0f // Send EIP-712 array
ledgerP2StructField ledgerParam2 = 0xff // Send EIP-712 struct field
ledgerP2FullImplementation ledgerParam2 = 0x01 // EIP-712 full implementation (typed data)
ledgerStatusNormalEnd ledgerStatus = 0x9000
ledgerEip155Size int = 3 // Size of the EIP-155 chain_id,r,s in unsigned transactions ledgerEip155Size int = 3 // Size of the EIP-155 chain_id,r,s in unsigned transactions
) )
var ledgerStatuses = map[ledgerStatus]string{
0x5515: "Device is locked",
0x6001: "Mode check fail",
0x6501: "TransactionType not supported",
0x6502: "Output buffer too small for chainId conversion",
0x6511: "Ethereum app not open",
//0x68xx: "Internal error (Please report)",
0x6982: "Security status not satisfied (Canceled by user)",
0x6983: "Wrong Data length",
0x6984: "Plugin not installed",
0x6985: "Condition not satisfied",
0x6A00: "Error without info",
0x6A80: "Invalid data",
0x6A84: "Insufficient memory",
0x6A88: "Data not found",
0x6B00: "Incorrect parameter P1 or P2",
0x6D00: "Incorrect parameter INS",
0x6E00: "Incorrect parameter CLA",
0x6F00: "Incorrect parameter CLA",
0x6F01: "Technical problem (Internal error, please report)",
0x911C: "Command code not supported (i.e. Ledger-PKI not yet available)",
}
// errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange // errLedgerReplyInvalidHeader is the error message returned by a Ledger data exchange
// if the device replies with a mismatching header. This usually means the device // if the device replies with a mismatching header. This usually means the device
// is in browser mode. // is in browser mode.
@ -72,6 +115,10 @@ var errLedgerReplyInvalidHeader = errors.New("ledger: invalid reply header")
// when a response does arrive, but it does not contain the expected data. // when a response does arrive, but it does not contain the expected data.
var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply") var errLedgerInvalidVersionReply = errors.New("ledger: invalid version reply")
// errLedgerInvalidStatus is the error message returned if the ledger doesn't respond with a
// 0x9000 status.
var errLedgerInvalidStatus = errors.New("ledger: invalid status")
// ledgerDriver implements the communication with a Ledger hardware wallet. // ledgerDriver implements the communication with a Ledger hardware wallet.
type ledgerDriver struct { type ledgerDriver struct {
device io.ReadWriter // USB device connection to communicate through device io.ReadWriter // USB device connection to communicate through
@ -119,7 +166,7 @@ func (w *ledgerDriver) Open(device io.ReadWriter, passphrase string) error {
_, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath) _, err := w.ledgerDerive(accounts.DefaultBaseDerivationPath)
if err != nil { if err != nil {
// Ethereum app is not running or in browser mode, nothing more to do, return // Ethereum app is not running or in browser mode, nothing more to do, return
if err == errLedgerReplyInvalidHeader { if errors.Is(err, errLedgerReplyInvalidHeader) {
w.browser = true w.browser = true
} }
return nil return nil
@ -141,7 +188,7 @@ func (w *ledgerDriver) Close() error {
// Heartbeat implements usbwallet.driver, performing a sanity check against the // Heartbeat implements usbwallet.driver, performing a sanity check against the
// Ledger to see if it's still online. // Ledger to see if it's still online.
func (w *ledgerDriver) Heartbeat() error { func (w *ledgerDriver) Heartbeat() error {
if _, err := w.ledgerVersion(); err != nil && err != errLedgerInvalidVersionReply { if _, err := w.ledgerVersion(); err != nil && !errors.Is(err, errLedgerInvalidVersionReply) {
w.failure = err w.failure = err
return err return err
} }
@ -166,7 +213,7 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
return common.Address{}, nil, accounts.ErrWalletClosed return common.Address{}, nil, accounts.ErrWalletClosed
} }
// Ensure the wallet is capable of signing the given transaction // Ensure the wallet is capable of signing the given transaction
if chainID != nil && (w.version[0] < 1 || (w.version[0] == 1 && w.version[1] == 0 && w.version[2] < 3)) { if chainID != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 2 {
//lint:ignore ST1005 brand name displayed on the console //lint:ignore ST1005 brand name displayed on the console
return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2]) return common.Address{}, nil, fmt.Errorf("Ledger v%d.%d.%d doesn't support signing this transaction, please update to v1.0.3 at least", w.version[0], w.version[1], w.version[2])
} }
@ -174,11 +221,11 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
return w.ledgerSign(path, tx, chainID) return w.ledgerSign(path, tx, chainID)
} }
// SignTypedMessage implements usbwallet.driver, sending the message to the Ledger and // SignTypedHash implements usbwallet.driver, sending the message to the Ledger and
// waiting for the user to sign or deny the transaction. // waiting for the user to sign or deny the transaction.
// //
// Note: this was introduced in the ledger 1.5.0 firmware // Note: this was introduced in the ledger 1.5.0 firmware
func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { func (w *ledgerDriver) SignTypedHash(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) {
// If the Ethereum app doesn't run, abort // If the Ethereum app doesn't run, abort
if w.offline() { if w.offline() {
return nil, accounts.ErrWalletClosed return nil, accounts.ErrWalletClosed
@ -189,7 +236,39 @@ func (w *ledgerDriver) SignTypedMessage(path accounts.DerivationPath, domainHash
return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2]) return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
} }
// All infos gathered and metadata checks out, request signing // All infos gathered and metadata checks out, request signing
return w.ledgerSignTypedMessage(path, domainHash, messageHash) return w.ledgerSignTypedHash(path, domainHash, messageHash)
}
// SignText implements usbwallet.driver, sending the message to the Ledger and
// waiting for the user to confirm or deny the signature.
func (w *ledgerDriver) SignText(path accounts.DerivationPath, text []byte) ([]byte, error) {
// If the Ethereum app doesn't run, abort
if w.offline() {
return nil, accounts.ErrWalletClosed
}
// Ensure the wallet is capable of signing the given transaction
if w.version[0] < 1 && w.version[1] < 5 {
//lint:ignore ST1005 brand name displayed on the console
return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
}
// All infos gathered and metadata checks out, request signing
return w.ledgerSignPersonalMessage(path, text)
}
// SignedTypedData implements usbwallet.driver, sending the message to the Ledger and
// waiting for the user to sign or deny signing an EIP-712 typed data struct.
func (w *ledgerDriver) SignedTypedData(path accounts.DerivationPath, data apitypes.TypedData) ([]byte, error) {
// If the Ethereum app doesn't run, abort
if w.offline() {
return nil, accounts.ErrWalletClosed
}
// Ensure the wallet is capable of signing the given transaction
if w.version[0] < 1 && w.version[1] < 5 {
//lint:ignore ST1005 brand name displayed on the console
return nil, fmt.Errorf("Ledger version >= 1.5.0 required for EIP-712 signing (found version v%d.%d.%d)", w.version[0], w.version[1], w.version[2])
}
// All infos gathered and metadata checks out, request signing
return w.ledgerSignTypedData(path, data)
} }
// ledgerVersion retrieves the current version of the Ethereum wallet app running // ledgerVersion retrieves the current version of the Ethereum wallet app running
@ -360,7 +439,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
// Send the request and wait for the response // Send the request and wait for the response
var ( var (
op = ledgerP1InitTransactionData p1 = ledgerP1InitTransactionData
reply []byte reply []byte
) )
@ -378,13 +457,13 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
chunk = len(payload) chunk = len(payload)
} }
// Send the chunk over, ensuring it's processed correctly // Send the chunk over, ensuring it's processed correctly
reply, err = w.ledgerExchange(ledgerOpSignTransaction, op, 0, payload[:chunk]) reply, err = w.ledgerExchange(ledgerOpSignTransaction, p1, ledgerP2ProcessAndStartFlow, payload[:chunk])
if err != nil { if err != nil {
return common.Address{}, nil, err return common.Address{}, nil, err
} }
// Shift the payload and ensure subsequent chunks are marked as such // Shift the payload and ensure subsequent chunks are marked as such
payload = payload[chunk:] payload = payload[chunk:]
op = ledgerP1ContTransactionData p1 = ledgerP1ContTransactionData
} }
// Extract the Ethereum signature and do a sanity validation // Extract the Ethereum signature and do a sanity validation
if len(reply) != crypto.SignatureLength { if len(reply) != crypto.SignatureLength {
@ -414,7 +493,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
return sender, signed, nil return sender, signed, nil
} }
// ledgerSignTypedMessage sends the transaction to the Ledger wallet, and waits for the user // ledgerSignTypedHash sends the transaction to the Ledger wallet, and waits for the user
// to confirm or deny the transaction. // to confirm or deny the transaction.
// //
// The signing protocol is defined as follows: // The signing protocol is defined as follows:
@ -441,7 +520,7 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
// signature V | 1 byte // signature V | 1 byte
// signature R | 32 bytes // signature R | 32 bytes
// signature S | 32 bytes // signature S | 32 bytes
func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) { func (w *ledgerDriver) ledgerSignTypedHash(derivationPath []uint32, domainHash []byte, messageHash []byte) ([]byte, error) {
// Flatten the derivation path into the Ledger request // Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath)) path := make([]byte, 1+4*len(derivationPath))
path[0] = byte(len(derivationPath)) path[0] = byte(len(derivationPath))
@ -454,13 +533,12 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas
// Send the request and wait for the response // Send the request and wait for the response
var ( var (
op = ledgerP1InitTypedMessageData
reply []byte reply []byte
err error err error
) )
// Send the message over, ensuring it's processed correctly // Send the message over, ensuring it's processed correctly
reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, op, 0, payload) reply, err = w.ledgerExchange(ledgerOpSignTypedMessage, ledgerP1InitTypedMessageData, ledgerP2V0Implementation, payload)
if err != nil { if err != nil {
return nil, err return nil, err
@ -474,6 +552,281 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas
return signature, nil return signature, nil
} }
// ledgerSignPersonalMessage sends the transaction to the Ledger wallet, and waits for the user
// to confirm or deny the transaction.
//
// The signing protocol is defined as follows:
//
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+-----------------------------+-----+---
// E0 | 08 | 00 | implementation version : 00 | variable | variable
//
// Where the input is:
//
// Description | Length
// -------------------------------------------------+----------
// Number of BIP 32 derivations to perform (max 10) | 1 byte
// First derivation index (big endian) | 4 bytes
// ... | 4 bytes
// Last derivation index (big endian) | 4 bytes
// text | arbitrary
//
// And the output data is:
//
// Description | Length
// ------------+---------
// signature V | 1 byte
// signature R | 32 bytes
// signature S | 32 bytes
func (w *ledgerDriver) ledgerSignPersonalMessage(derivationPath []uint32, text []byte) ([]byte, error) {
// Flatten the derivation path into the Ledger request
path := make([]byte, 5+4*len(derivationPath))
path[0] = byte(len(derivationPath))
for i, component := range derivationPath {
binary.BigEndian.PutUint32(path[1+4*i:], component)
}
binary.BigEndian.PutUint32(path[1+4*len(derivationPath):], uint32(len(text)))
// Create the 712 message
payload := append(path, text...)
// Send the request and wait for the response
var (
reply []byte
err error
)
// Send the message over, ensuring it's processed correctly
reply, err = w.ledgerExchange(ledgerOpSignPersonalMessage, ledgerP1InitTransactionData, 0, payload)
if err != nil {
return nil, err
}
// Extract the Ethereum signature and do a sanity validation
if len(reply) != crypto.SignatureLength {
return nil, errors.New("reply lacks signature")
}
signature := append(reply[1:], reply[0])
return signature, nil
}
// ledgerSignTypedData sends the transaction to the Ledger wallet, and waits for the user
// to confirm or deny the transaction.
//
// The typed data struct fields and values need to be sent to the Ledger first.
// See https://github.com/LedgerHQ/app-ethereum/blob/develop/doc/eip712.md.
//
// After the data is sent, the signing protocol is defined as follows:
//
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+-----------------------------+-----+---
// E0 | 0C | 00 | implementation version : 00 | variable | variable
//
// Where the input is:
//
// Description | Length
// -------------------------------------------------+----------
// Number of BIP 32 derivations to perform (max 10) | 1 byte
// First derivation index (big endian) | 4 bytes
// ... | 4 bytes
// Last derivation index (big endian) | 4 bytes
//
// And the output data is:
//
// Description | Length
// ------------+---------
// signature V | 1 byte
// signature R | 32 bytes
// signature S | 32 bytes
func (w *ledgerDriver) ledgerSignTypedData(derivationPath []uint32, data apitypes.TypedData) ([]byte, error) {
// Check if the EIP712Domain and primary type are present in the data
domainStruct := data.Types["EIP712Domain"]
if domainStruct == nil {
return nil, fmt.Errorf("EIP712Domain type is required")
}
primaryType := data.Types[data.PrimaryType]
if primaryType == nil {
return nil, fmt.Errorf("primary type %s not found in types", data.PrimaryType)
}
// sendField is a function for sending an EIP-712 struct field name + type
sendField := func(field apitypes.Type) error {
dt, name, byteLength, arrays, err := parseType(data, field)
if err != nil {
return err
}
typeDesc := byte(dt)
var typeName []byte
var typeSize []byte
switch dt {
case CustomType:
typeName = append([]byte{byte(len(name))}, []byte(name)...)
case IntType, UintType, FixedBytesType:
typeSize = []byte{byte(byteLength)}
typeDesc |= 0x40
default:
}
var arrayLevels []byte
if len(arrays) > 0 {
typeDesc |= 0x80
arrayLevels = []byte{byte(len(arrays))}
for _, length := range arrays {
if length == nil {
arrayLevels = append(arrayLevels, 0)
} else {
arrayLevels = append(arrayLevels, 1, byte(*length))
}
}
}
payload := []byte{typeDesc}
payload = append(payload, typeName...)
payload = append(payload, typeSize...)
payload = append(payload, arrayLevels...)
payload = append(payload, byte(len(field.Name)))
payload = append(payload, []byte(field.Name)...)
_, err = w.ledgerExchange(ledgerOpEip712SendStructDef, 0, ledgerP2StructField, payload)
return err
}
// sendValue is a recursive function that sends the value of a field
var sendValue func(t, name string, value interface{}) error
sendValue = func(t, name string, value interface{}) error {
if value == nil {
return fmt.Errorf("nil value for field %s", name)
}
if strings.HasSuffix(t, "]") {
a, ok := value.([]interface{})
if !ok {
return fmt.Errorf("expected array for field %s, got %T", name, value)
}
if _, err := w.ledgerExchange(ledgerOpEip712SendStructImpl, ledgerP1CompleteSend, ledgerP2Array, []byte{byte(len(a))}); err != nil {
return fmt.Errorf("failed to send array length: %w", err)
}
t = t[:strings.LastIndex(t, "[")]
for _, item := range a {
if err := sendValue(t, name, item); err != nil {
return fmt.Errorf("failed to send array item: %w", err)
}
}
return nil
}
s := data.Types[t]
if s != nil {
m, ok := value.(map[string]interface{})
if !ok {
return fmt.Errorf("expected struct for field %s, got %T", name, value)
}
for _, field := range s {
if err := sendValue(field.Type, field.Name, m[field.Name]); err != nil {
return fmt.Errorf("failed to send struct field %s: %w", field.Name, err)
}
}
return nil
}
var enc []byte
var err error
switch v := value.(type) {
case string:
if t == "string" {
enc = []byte(v)
} else if strings.HasPrefix(v, "0x") {
enc, err = hex.DecodeString(v[2:])
if err != nil {
return fmt.Errorf("failed to decode hex string for field %s: %w", name, err)
}
} else {
return fmt.Errorf("invalid string value for field %s: %s", name, v)
}
case bool:
if v {
enc = []byte{1}
} else {
enc = []byte{0}
}
case float64:
enc = new(big.Int).SetInt64(int64(v)).Bytes()
case *math.HexOrDecimal256:
if v == nil {
return fmt.Errorf("nil value for field %s", name)
}
h := big.Int(*v)
enc = (&h).Bytes()
default:
return fmt.Errorf("unsupported type for field %s: %T", name, value)
}
chunk := 255
payload := binary.BigEndian.AppendUint16([]byte{}, uint16(len(enc)))
payload = append(payload, enc...)
for len(payload) > 0 {
p1 := ledgerP1PartialSend
if chunk >= len(payload) {
chunk = len(payload)
p1 = ledgerP1CompleteSend
}
if _, err := w.ledgerExchange(ledgerOpEip712SendStructImpl, p1, ledgerP2StructField, payload[:chunk]); err != nil {
return fmt.Errorf("failed to send field %s: %w", name, err)
}
payload = payload[chunk:]
}
return nil
}
// first send all the EIP-712 struct definitions
for name, fields := range data.Types {
_, err := w.ledgerExchange(ledgerOpEip712SendStructDef, 0, ledgerP2StructName, []byte(name))
if err != nil {
return nil, fmt.Errorf("failed to send type name %s: %w", name, err)
}
for _, field := range fields {
if err := sendField(field); err != nil {
return nil, fmt.Errorf("failed to send field %s: %w", field.Name, err)
}
}
}
// send the EIP-712 domain field values
if _, err := w.ledgerExchange(ledgerOpEip712SendStructImpl, ledgerP1CompleteSend, ledgerP2RootStruct, []byte("EIP712Domain")); err != nil {
return nil, fmt.Errorf("failed to send domain type name: %w", err)
}
if err := sendValue("EIP712Domain", "domain", data.Domain.Map()); err != nil {
return nil, fmt.Errorf("failed to send domain fields: %w", err)
}
// send the message field values
if _, err := w.ledgerExchange(ledgerOpEip712SendStructImpl, ledgerP1CompleteSend, ledgerP2RootStruct, []byte(data.PrimaryType)); err != nil {
return nil, fmt.Errorf("failed to send primary type name: %w", err)
}
if err := sendValue(data.PrimaryType, "message", data.Message); err != nil {
return nil, fmt.Errorf("failed to send primary type fields: %w", err)
}
// Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath))
path[0] = byte(len(derivationPath))
for i, component := range derivationPath {
binary.BigEndian.PutUint32(path[1+4*i:], component)
}
// Send the message over, ensuring it's processed correctly
reply, err := w.ledgerExchange(ledgerOpSignTypedMessage, 0, ledgerP2FullImplementation, path)
if err != nil {
return nil, err
}
// Extract the Ethereum signature and do a sanity validation
if len(reply) != crypto.SignatureLength {
return nil, fmt.Errorf("invalid signature length: %d", len(reply))
}
signature := append(reply[1:], reply[0])
return signature, nil
}
// ledgerExchange performs a data exchange with the Ledger wallet, sending it a // ledgerExchange performs a data exchange with the Ledger wallet, sending it a
// message and retrieving the response. // message and retrieving the response.
// //
@ -508,6 +861,16 @@ func (w *ledgerDriver) ledgerSignTypedMessage(derivationPath []uint32, domainHas
// APDU length | 1 byte // APDU length | 1 byte
// Optional APDU data | arbitrary // Optional APDU data | arbitrary
func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) { func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
for i := 1; ; i++ {
res, err := w._ledgerExchange(opcode, p1, p2, data)
// on failure, try the exchange 3 times in total
if err == nil || i == 3 {
return res, err
}
}
}
func (w *ledgerDriver) _ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
// Construct the message payload, possibly split into multiple chunks // Construct the message payload, possibly split into multiple chunks
apdu := make([]byte, 2, 7+len(data)) apdu := make([]byte, 2, 7+len(data))
@ -569,5 +932,16 @@ func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l
break break
} }
} }
if len(reply) < 2 {
return nil, errLedgerInvalidStatus
}
status := ledgerStatus(binary.BigEndian.Uint16(reply[len(reply)-2:]))
if status != ledgerStatusNormalEnd {
s := ledgerStatuses[status]
if s == "" {
s = "unknown error"
}
return nil, fmt.Errorf("%w: 0x%s (%s)", errLedgerInvalidStatus, strconv.FormatUint(uint64(status), 16), s)
}
return reply[:len(reply)-2], nil return reply[:len(reply)-2], nil
} }

View file

@ -27,6 +27,7 @@ import (
"io" "io"
"math" "math"
"math/big" "math/big"
"reflect"
"github.com/ethereum/go-ethereum/accounts" "github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/usbwallet/trezor" "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
@ -34,29 +35,32 @@ import (
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/signer/core/apitypes"
pin "github.com/reserve-protocol/trezor"
"google.golang.org/protobuf/proto" "google.golang.org/protobuf/proto"
) )
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In
// this case, the calling application should display a pinpad and send back the
// encoded passphrase.
var ErrTrezorPINNeeded = errors.New("trezor: pin needed")
// ErrTrezorPassphraseNeeded is returned if opening the trezor requires a passphrase
var ErrTrezorPassphraseNeeded = errors.New("trezor: passphrase needed")
// errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange // errTrezorReplyInvalidHeader is the error message returned by a Trezor data exchange
// if the device replies with a mismatching header. This usually means the device // if the device replies with a mismatching header. This usually means the device
// is in browser mode. // is in browser mode.
var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header") var errTrezorReplyInvalidHeader = errors.New("trezor: invalid reply header")
type TrezorFailure struct {
*trezor.Failure
}
// Error implements the error interface for the TrezorFailure type, returning
// a formatted error message containing the failure reason.
func (f *TrezorFailure) Error() string {
return fmt.Sprintf("trezor: %s", f.GetMessage())
}
// trezorDriver implements the communication with a Trezor hardware wallet. // trezorDriver implements the communication with a Trezor hardware wallet.
type trezorDriver struct { type trezorDriver struct {
device io.ReadWriter // USB device connection to communicate through device io.ReadWriter // USB device connection to communicate through
version [3]uint32 // Current version of the Trezor firmware version [3]uint32 // Current version of the Trezor firmware
label string // Current textual label of the Trezor device label string // Current textual label of the Trezor device
pinwait bool // Flags whether the device is waiting for PIN entry passphrase string
passphrasewait bool // Flags whether the device is waiting for passphrase entry
failure error // Any failure that would make the device unusable failure error // Any failure that would make the device unusable
log log.Logger // Contextual logger to tag the trezor with its id log log.Logger // Contextual logger to tag the trezor with its id
} }
@ -77,33 +81,18 @@ func (w *trezorDriver) Status() (string, error) {
if w.device == nil { if w.device == nil {
return "Closed", w.failure return "Closed", w.failure
} }
if w.pinwait {
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
}
return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure return fmt.Sprintf("Trezor v%d.%d.%d '%s' online", w.version[0], w.version[1], w.version[2], w.label), w.failure
} }
// Open implements usbwallet.driver, attempting to initialize the connection to // Open implements usbwallet.driver, attempting to initialize the connection to
// the Trezor hardware wallet. Initializing the Trezor is a two or three phase operation: // the Trezor hardware wallet.
// - The first phase is to initialize the connection and read the wallet's
// features. This phase is invoked if the provided passphrase is empty. The
// device will display the pinpad as a result and will return an appropriate
// error to notify the user that a second open phase is needed.
// - The second phase is to unlock access to the Trezor, which is done by the
// user actually providing a passphrase mapping a keyboard keypad to the pin
// number of the user (shuffled according to the pinpad displayed).
// - If needed the device will ask for passphrase which will require calling
// open again with the actual passphrase (3rd phase)
func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error { func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.device, w.failure = device, nil w.device, w.passphrase, w.failure = device, passphrase, nil
// If phase 1 is requested, init the connection and wait for user callback if _, err := w.trezorExchange(&trezor.EndSession{}, new(trezor.Success)); err != nil {
if passphrase == "" && !w.passphrasewait { return err
// If we're already waiting for a PIN entry, insta-return
if w.pinwait {
return ErrTrezorPINNeeded
} }
// Initialize a connection to the device
features := new(trezor.Features) features := new(trezor.Features)
if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil { if _, err := w.trezorExchange(&trezor.Initialize{}, features); err != nil {
return err return err
@ -111,53 +100,13 @@ func (w *trezorDriver) Open(device io.ReadWriter, passphrase string) error {
w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()} w.version = [3]uint32{features.GetMajorVersion(), features.GetMinorVersion(), features.GetPatchVersion()}
w.label = features.GetLabel() w.label = features.GetLabel()
// Do a manual ping, forcing the device to ask for its PIN and Passphrase return w.Heartbeat()
askPin := true
askPassphrase := true
res, err := w.trezorExchange(&trezor.Ping{PinProtection: &askPin, PassphraseProtection: &askPassphrase}, new(trezor.PinMatrixRequest), new(trezor.PassphraseRequest), new(trezor.Success))
if err != nil {
return err
}
// Only return the PIN request if the device wasn't unlocked until now
switch res {
case 0:
w.pinwait = true
return ErrTrezorPINNeeded
case 1:
w.pinwait = false
w.passphrasewait = true
return ErrTrezorPassphraseNeeded
case 2:
return nil // responded with trezor.Success
}
}
// Phase 2 requested with actual PIN entry
if w.pinwait {
w.pinwait = false
res, err := w.trezorExchange(&trezor.PinMatrixAck{Pin: &passphrase}, new(trezor.Success), new(trezor.PassphraseRequest))
if err != nil {
w.failure = err
return err
}
if res == 1 {
w.passphrasewait = true
return ErrTrezorPassphraseNeeded
}
} else if w.passphrasewait {
w.passphrasewait = false
if _, err := w.trezorExchange(&trezor.PassphraseAck{Passphrase: &passphrase}, new(trezor.Success)); err != nil {
w.failure = err
return err
}
}
return nil
} }
// Close implements usbwallet.driver, cleaning up and metadata maintained within // Close implements usbwallet.driver, cleaning up and metadata maintained within
// the Trezor driver. // the Trezor driver.
func (w *trezorDriver) Close() error { func (w *trezorDriver) Close() error {
w.version, w.label, w.pinwait = [3]uint32{}, "", false w.version, w.label = [3]uint32{}, ""
return nil return nil
} }
@ -186,8 +135,236 @@ func (w *trezorDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
return w.trezorSign(path, tx, chainID) return w.trezorSign(path, tx, chainID)
} }
func (w *trezorDriver) SignTypedMessage(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) { func (w *trezorDriver) SignTypedHash(path accounts.DerivationPath, domainHash []byte, messageHash []byte) ([]byte, error) {
return nil, accounts.ErrNotSupported if w.device == nil {
return nil, accounts.ErrWalletClosed
}
response := new(trezor.EthereumTypedDataSignature)
_, err := w.trezorExchange(&trezor.EthereumSignTypedHash{
AddressN: path,
DomainSeparatorHash: domainHash,
MessageHash: messageHash,
}, response)
if err != nil {
return nil, err
}
return response.Signature, nil
}
func (w *trezorDriver) SignText(path accounts.DerivationPath, text []byte) ([]byte, error) {
if w.device == nil {
return nil, accounts.ErrWalletClosed
}
response := new(trezor.EthereumMessageSignature)
_, err := w.trezorExchange(&trezor.EthereumSignMessage{
AddressN: path,
Message: text,
}, response)
if err != nil {
return nil, err
}
return response.Signature, nil
}
func (w *trezorDriver) SignedTypedData(path accounts.DerivationPath, data apitypes.TypedData) ([]byte, error) {
if w.device == nil {
return nil, accounts.ErrWalletClosed
}
_, hashes, err := apitypes.TypedDataAndHash(data)
if err != nil {
return nil, fmt.Errorf("trezor: error hashing typed data: %w", err)
}
domainHash, messageHash := hashes[2:34], hashes[34:66]
if w.version[0] == 1 {
// legacy Trezor devices don't support typed data; fallback to hash signing:
return w.SignTypedHash(path, []byte(domainHash), []byte(messageHash))
}
if w.version[0] == 2 && (w.version[1] < 9 || (w.version[1] == 9 && w.version[2] == 0)) {
// ShowMessageHash was introduced in Trezor firmware v2.9.1
return nil, fmt.Errorf("trezor: typed data signing requires firmware v2.9.1 or newer")
}
signature := new(trezor.EthereumTypedDataSignature)
structRequest := new(trezor.EthereumTypedDataStructRequest)
valueRequest := new(trezor.EthereumTypedDataValueRequest)
var req proto.Message = &trezor.EthereumSignTypedData{
AddressN: path,
PrimaryType: &data.PrimaryType,
ShowMessageHash: []byte(messageHash),
}
nestedArray := false
for {
n, err := w.trezorExchange(req, signature, structRequest, valueRequest)
if err != nil {
var trezorFailure *TrezorFailure
if nestedArray && errors.As(err, &trezorFailure) &&
trezorFailure.Code != nil && *trezorFailure.Code == trezor.Failure_Failure_FirmwareError {
return nil, fmt.Errorf("trezor: nested arrays are not supported by this firmware version: %w", err)
}
return nil, err
}
nestedArray = false
switch n {
case 0:
// No additional data needed, return the signature
return signature.Signature, nil
case 1:
fields := data.Types[structRequest.GetName()]
if len(fields) == 0 {
return nil, fmt.Errorf("trezor: no fields for struct %s", structRequest.GetName())
}
ack := &trezor.EthereumTypedDataStructAck{
Members: make([]*trezor.EthereumTypedDataStructAck_EthereumStructMember, len(fields)),
}
for i, field := range fields {
dt, name, byteLength, arrays, err := parseType(data, field)
if err != nil {
return nil, err
}
ubyteLength := uint32(byteLength)
t := &trezor.EthereumTypedDataStructAck_EthereumFieldType{}
inner := t
for i := len(arrays) - 1; i >= 0; i-- {
dataType := trezor.EthereumTypedDataStructAck_ARRAY
inner.DataType = &dataType
if arrays[i] != nil {
length := uint32(*arrays[i])
inner.Size = &length
}
inner.EntryType = &trezor.EthereumTypedDataStructAck_EthereumFieldType{}
inner = inner.EntryType
}
var dataType trezor.EthereumTypedDataStructAck_EthereumDataType
switch dt {
case CustomType:
inner.StructName = &name
dataType = trezor.EthereumTypedDataStructAck_STRUCT
members := uint32(len(data.Types[name]))
inner.Size = &members
case IntType:
dataType = trezor.EthereumTypedDataStructAck_INT
inner.Size = &ubyteLength
case UintType:
dataType = trezor.EthereumTypedDataStructAck_UINT
inner.Size = &ubyteLength
case AddressType:
dataType = trezor.EthereumTypedDataStructAck_ADDRESS
case BoolType:
dataType = trezor.EthereumTypedDataStructAck_BOOL
case StringType:
dataType = trezor.EthereumTypedDataStructAck_STRING
case FixedBytesType:
dataType = trezor.EthereumTypedDataStructAck_BYTES
inner.Size = &ubyteLength
case BytesType:
dataType = trezor.EthereumTypedDataStructAck_BYTES
}
inner.DataType = &dataType
ack.Members[i] = &trezor.EthereumTypedDataStructAck_EthereumStructMember{
Name: &field.Name,
Type: t,
}
}
req = ack
case 2:
structType := data.Types[data.PrimaryType]
structValue := data.Message
if valueRequest.MemberPath[0] == 0 {
// populate with domain info
structType = data.Types["EIP712Domain"]
structValue = data.Domain.Map()
}
var value []byte
for i := 1; i < len(valueRequest.MemberPath); i++ {
p := valueRequest.MemberPath[i]
if structType == nil {
return nil, fmt.Errorf("trezor: no struct type for path %v", path)
}
if int(p) >= len(structType) {
return nil, fmt.Errorf("trezor: invalid field index %d for struct %s", p, structRequest.GetName())
}
field := structType[p]
nextValue := structValue[field.Name]
dt, name, byteLength, arrays, err := parseType(data, field)
if err != nil {
return nil, err
}
if len(arrays) > 1 {
nestedArray = true
}
for j := 0; j < len(arrays) && i < len(valueRequest.MemberPath)-1; i, j = i+1, j+1 {
k := reflect.TypeOf(nextValue).Kind()
if !(k == reflect.Array || k == reflect.Slice) {
return nil, fmt.Errorf("trezor: expected array at path %v, got %T", valueRequest.MemberPath[:i+1], nextValue)
}
a := reflect.ValueOf(nextValue)
p = valueRequest.MemberPath[i+1]
if int(p) >= a.Len() {
return nil, fmt.Errorf("trezor: invalid array index %d for path %v", p, valueRequest.MemberPath[:i+1])
}
nextValue = a.Index(int(p)).Interface()
}
k := reflect.TypeOf(nextValue).Kind()
if i < len(valueRequest.MemberPath)-1 {
if reflect.TypeOf(nextValue).Kind() != reflect.Map {
return nil, fmt.Errorf("trezor: expected map at path %v, got %T", valueRequest.MemberPath[:i+1], nextValue)
}
structType = data.Types[name]
structValue = nextValue.(apitypes.TypedDataMessage)
} else if k == reflect.Array || k == reflect.Slice {
// Array value, return length as uint16
value = binary.BigEndian.AppendUint16([]byte{}, uint16(reflect.ValueOf(nextValue).Len()))
} else {
// Last value, encode it as a primitive value
switch dt {
case CustomType:
return nil, fmt.Errorf("trezor: cannot encode custom type %s at path %v", name, valueRequest.MemberPath[:i+1])
case IntType, UintType, AddressType, FixedBytesType:
if str, ok := nextValue.(string); ok {
value = common.FromHex(str)
} else if f, ok := nextValue.(float64); ok {
value = new(big.Int).SetInt64(int64(f)).Bytes()
}
if len(value) > byteLength {
return nil, fmt.Errorf("trezor: value at path %v is too long (%d bytes, expected %d)", valueRequest.MemberPath[:i+1], len(value), byteLength)
}
for len(value) < byteLength {
value = append([]byte{0}, value...)
}
case BoolType:
if b, ok := nextValue.(bool); ok {
if b {
value = []byte{1}
} else {
value = []byte{0}
}
} else {
return nil, fmt.Errorf("trezor: expected bool at path %v, got %T", valueRequest.MemberPath[:i+1], nextValue)
}
case StringType:
if str, ok := nextValue.(string); ok {
value = []byte(str)
} else {
return nil, fmt.Errorf("trezor: expected string at path %v, got %T", valueRequest.MemberPath[:i+1], nextValue)
}
case BytesType:
if str, ok := nextValue.(string); ok {
value = common.FromHex(str)
} else {
return nil, fmt.Errorf("trezor: expected bytes at path %v, got %T", valueRequest.MemberPath[:i+1], nextValue)
}
}
}
}
req = &trezor.EthereumTypedDataValueAck{
Value: value,
}
default:
return nil, fmt.Errorf("trezor: unexpected reply index %d", n)
}
}
} }
// trezorDerive sends a derivation request to the Trezor device and returns the // trezorDerive sends a derivation request to the Trezor device and returns the
@ -197,10 +374,7 @@ func (w *trezorDriver) trezorDerive(derivationPath []uint32) (common.Address, er
if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil { if _, err := w.trezorExchange(&trezor.EthereumGetAddress{AddressN: derivationPath}, address); err != nil {
return common.Address{}, err return common.Address{}, err
} }
if addr := address.GetAddressBin(); len(addr) > 0 { // Older firmwares use binary formats if addr := address.GetAddress(); len(addr) > 0 {
return common.BytesToAddress(addr), nil
}
if addr := address.GetAddressHex(); len(addr) > 0 { // Newer firmwares use hexadecimal formats
return common.HexToAddress(addr), nil return common.HexToAddress(addr), nil
} }
return common.Address{}, errors.New("missing derived address") return common.Address{}, errors.New("missing derived address")
@ -224,8 +398,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
if to := tx.To(); to != nil { if to := tx.To(); to != nil {
// Non contract deploy, set recipient explicitly // Non contract deploy, set recipient explicitly
hex := to.Hex() hex := to.Hex()
request.ToHex = &hex // Newer firmwares (old will ignore) request.To = &hex
request.ToBin = (*to)[:] // Older firmwares (new will ignore)
} }
if length > 1024 { // Send the data chunked if that was requested if length > 1024 { // Send the data chunked if that was requested
request.DataInitialChunk, data = data[:1024], data[1024:] request.DataInitialChunk, data = data[:1024], data[1024:]
@ -233,7 +406,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
request.DataInitialChunk, data = data, nil request.DataInitialChunk, data = data, nil
} }
if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?) if chainID != nil { // EIP-155 transaction, set chain ID explicitly (only 32 bit is supported!?)
id := uint32(chainID.Int64()) id := chainID.Uint64()
request.ChainId = &id request.ChainId = &id
} }
// Send the initiation message and stream content until a signature is returned // Send the initiation message and stream content until a signature is returned
@ -250,11 +423,7 @@ func (w *trezorDriver) trezorSign(derivationPath []uint32, tx *types.Transaction
} }
} }
// Extract the Ethereum signature and do a sanity validation // Extract the Ethereum signature and do a sanity validation
if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 { if len(response.GetSignatureR()) == 0 || len(response.GetSignatureS()) == 0 || response.GetSignatureV() == 0 {
return common.Address{}, nil, errors.New("reply lacks signature")
} else if response.GetSignatureV() == 0 && int(chainID.Int64()) <= (math.MaxUint32-36)/2 {
// for chainId >= (MaxUint32-36)/2, Trezor returns signature bit only
// https://github.com/trezor/trezor-mcu/pull/399
return common.Address{}, nil, errors.New("reply lacks signature") return common.Address{}, nil, errors.New("reply lacks signature")
} }
signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV())) signature := append(append(response.GetSignatureR(), response.GetSignatureS()...), byte(response.GetSignatureV()))
@ -361,12 +530,22 @@ func (w *trezorDriver) trezorExchange(req proto.Message, results ...proto.Messag
if err := proto.Unmarshal(reply, failure); err != nil { if err := proto.Unmarshal(reply, failure); err != nil {
return 0, err return 0, err
} }
return 0, errors.New("trezor: " + failure.GetMessage()) return 0, &TrezorFailure{Failure: failure}
} }
if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) { if kind == uint16(trezor.MessageType_MessageType_ButtonRequest) {
// Trezor is waiting for user confirmation, ack and wait for the next message // Trezor is waiting for user confirmation, ack and wait for the next message
return w.trezorExchange(&trezor.ButtonAck{}, results...) return w.trezorExchange(&trezor.ButtonAck{}, results...)
} }
if kind == uint16(trezor.MessageType_MessageType_PinMatrixRequest) {
p, err := pin.GetPIN("Please enter your Trezor PIN")
if err != nil {
return 0, err
}
return w.trezorExchange(&trezor.PinMatrixAck{Pin: &p}, results...)
}
if kind == uint16(trezor.MessageType_MessageType_PassphraseRequest) {
return w.trezorExchange(&trezor.PassphraseAck{Passphrase: &w.passphrase}, results...)
}
for i, res := range results { for i, res := range results {
if trezor.Type(res) == kind { if trezor.Type(res) == kind {
return i, proto.Unmarshal(reply, res) return i, proto.Unmarshal(reply, res)

File diff suppressed because it is too large Load diff

View file

@ -1,18 +1,26 @@
// This file originates from the SatoshiLabs Trezor `common` repository at: // This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/messages-common.proto // https://github.com/trezor/trezor-common/blob/master/protob/messages-common.proto
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. // dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
syntax = "proto2"; syntax = "proto2";
package hw.trezor.messages.common; package hw.trezor.messages.common;
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"; 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 = "TrezorMessageCommon";
import "options.proto";
option (include_in_bitcoin_only) = true;
/** /**
* Response: Success of the previous request * Response: Success of the previous request
* @end * @end
*/ */
message Success { message Success {
optional string message = 1; // human readable description of action or request-specific payload optional string message = 1 [default=""]; // human readable description of action or request-specific payload
} }
/** /**
@ -35,6 +43,12 @@ message Failure {
Failure_NotEnoughFunds = 10; Failure_NotEnoughFunds = 10;
Failure_NotInitialized = 11; Failure_NotInitialized = 11;
Failure_PinMismatch = 12; Failure_PinMismatch = 12;
Failure_WipeCodeMismatch = 13;
Failure_InvalidSession = 14;
Failure_Busy = 15;
Failure_ThpUnallocatedSession = 16;
Failure_InvalidProtocol = 17;
Failure_BufferError = 18;
Failure_FirmwareError = 99; Failure_FirmwareError = 99;
} }
} }
@ -45,8 +59,14 @@ message Failure {
* @next ButtonAck * @next ButtonAck
*/ */
message ButtonRequest { message ButtonRequest {
optional ButtonRequestType code = 1; optional ButtonRequestType code = 1; // enum identifier of the screen (deprecated)
optional string data = 2; optional uint32 pages = 2; // if the screen is paginated, number of pages
// this existed briefly: https://github.com/trezor/trezor-firmware/commit/1012ee8497b241e8ca559e386d936fa549bc0357
reserved 3;
optional string name = 4; // name of the screen
/** /**
* Type of button request * Type of button request
*/ */
@ -64,8 +84,13 @@ message ButtonRequest {
ButtonRequest_PublicKey = 11; ButtonRequest_PublicKey = 11;
ButtonRequest_MnemonicWordCount = 12; ButtonRequest_MnemonicWordCount = 12;
ButtonRequest_MnemonicInput = 13; ButtonRequest_MnemonicInput = 13;
ButtonRequest_PassphraseType = 14; _Deprecated_ButtonRequest_PassphraseType = 14 [deprecated=true];
ButtonRequest_UnknownDerivationPath = 15; ButtonRequest_UnknownDerivationPath = 15;
ButtonRequest_RecoveryHomepage = 16;
ButtonRequest_Success = 17;
ButtonRequest_Warning = 18;
ButtonRequest_PassphraseEntry = 19;
ButtonRequest_PinEntry = 20;
} }
} }
@ -90,6 +115,8 @@ message PinMatrixRequest {
PinMatrixRequestType_Current = 1; PinMatrixRequestType_Current = 1;
PinMatrixRequestType_NewFirst = 2; PinMatrixRequestType_NewFirst = 2;
PinMatrixRequestType_NewSecond = 3; PinMatrixRequestType_NewSecond = 3;
PinMatrixRequestType_WipeCodeFirst = 4;
PinMatrixRequestType_WipeCodeSecond = 5;
} }
} }
@ -107,31 +134,36 @@ message PinMatrixAck {
* @next PassphraseAck * @next PassphraseAck
*/ */
message PassphraseRequest { message PassphraseRequest {
optional bool on_device = 1; // passphrase is being entered on the device optional bool _on_device = 1 [deprecated=true]; // <2.3.0
} }
/** /**
* Request: Send passphrase back * Request: Send passphrase back
* @next PassphraseStateRequest * @auxend
*/ */
message PassphraseAck { message PassphraseAck {
optional string passphrase = 1; optional string passphrase = 1;
optional bytes state = 2; // expected device state optional bytes _state = 2 [deprecated=true]; // <2.3.0
optional bool on_device = 3; // user wants to enter passphrase on the device
} }
/** /**
* Response: Device awaits passphrase state * Response: Device awaits passphrase state
* @next PassphraseStateAck * Deprecated in 2.3.0
* @next Deprecated_PassphraseStateAck
*/ */
message PassphraseStateRequest { message Deprecated_PassphraseStateRequest {
option deprecated = true;
optional bytes state = 1; // actual device state optional bytes state = 1; // actual device state
} }
/** /**
* Request: Send passphrase state back * Request: Send passphrase state back
* Deprecated in 2.3.0
* @auxend * @auxend
*/ */
message PassphraseStateAck { message Deprecated_PassphraseStateAck {
option deprecated = true;
} }
/** /**
@ -145,5 +177,5 @@ message HDNodeType {
required uint32 child_num = 3; required uint32 child_num = 3;
required bytes chain_code = 4; required bytes chain_code = 4;
optional bytes private_key = 5; optional bytes private_key = 5;
optional bytes public_key = 6; required bytes public_key = 6;
} }

View file

@ -0,0 +1,608 @@
// This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum-eip712.proto
// dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v4.25.3
// source: messages-ethereum-eip712.proto
package trezor
import (
reflect "reflect"
sync "sync"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type EthereumTypedDataStructAck_EthereumDataType int32
const (
EthereumTypedDataStructAck_UINT EthereumTypedDataStructAck_EthereumDataType = 1
EthereumTypedDataStructAck_INT EthereumTypedDataStructAck_EthereumDataType = 2
EthereumTypedDataStructAck_BYTES EthereumTypedDataStructAck_EthereumDataType = 3
EthereumTypedDataStructAck_STRING EthereumTypedDataStructAck_EthereumDataType = 4
EthereumTypedDataStructAck_BOOL EthereumTypedDataStructAck_EthereumDataType = 5
EthereumTypedDataStructAck_ADDRESS EthereumTypedDataStructAck_EthereumDataType = 6
EthereumTypedDataStructAck_ARRAY EthereumTypedDataStructAck_EthereumDataType = 7
EthereumTypedDataStructAck_STRUCT EthereumTypedDataStructAck_EthereumDataType = 8
)
// Enum value maps for EthereumTypedDataStructAck_EthereumDataType.
var (
EthereumTypedDataStructAck_EthereumDataType_name = map[int32]string{
1: "UINT",
2: "INT",
3: "BYTES",
4: "STRING",
5: "BOOL",
6: "ADDRESS",
7: "ARRAY",
8: "STRUCT",
}
EthereumTypedDataStructAck_EthereumDataType_value = map[string]int32{
"UINT": 1,
"INT": 2,
"BYTES": 3,
"STRING": 4,
"BOOL": 5,
"ADDRESS": 6,
"ARRAY": 7,
"STRUCT": 8,
}
)
func (x EthereumTypedDataStructAck_EthereumDataType) Enum() *EthereumTypedDataStructAck_EthereumDataType {
p := new(EthereumTypedDataStructAck_EthereumDataType)
*p = x
return p
}
func (x EthereumTypedDataStructAck_EthereumDataType) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (EthereumTypedDataStructAck_EthereumDataType) Descriptor() protoreflect.EnumDescriptor {
return file_messages_ethereum_eip712_proto_enumTypes[0].Descriptor()
}
func (EthereumTypedDataStructAck_EthereumDataType) Type() protoreflect.EnumType {
return &file_messages_ethereum_eip712_proto_enumTypes[0]
}
func (x EthereumTypedDataStructAck_EthereumDataType) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Do not use.
func (x *EthereumTypedDataStructAck_EthereumDataType) UnmarshalJSON(b []byte) error {
num, err := protoimpl.X.UnmarshalJSONEnum(x.Descriptor(), b)
if err != nil {
return err
}
*x = EthereumTypedDataStructAck_EthereumDataType(num)
return nil
}
// Deprecated: Use EthereumTypedDataStructAck_EthereumDataType.Descriptor instead.
func (EthereumTypedDataStructAck_EthereumDataType) EnumDescriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{2, 0}
}
// *
// Request: Ask device to sign typed data
// @start
// @next EthereumTypedDataStructRequest
// @next EthereumTypedDataValueRequest
// @next EthereumTypedDataSignature
// @next Failure
type EthereumSignTypedData struct {
state protoimpl.MessageState `protogen:"open.v1"`
AddressN []uint32 `protobuf:"varint,1,rep,name=address_n,json=addressN" json:"address_n,omitempty"` // BIP-32 path to derive the key from master node
PrimaryType *string `protobuf:"bytes,2,req,name=primary_type,json=primaryType" json:"primary_type,omitempty"` // name of the root message struct
MetamaskV4Compat *bool `protobuf:"varint,3,opt,name=metamask_v4_compat,json=metamaskV4Compat,def=1" json:"metamask_v4_compat,omitempty"` // use MetaMask v4 (see https://github.com/MetaMask/eth-sig-util/issues/106)
Definitions *EthereumDefinitions `protobuf:"bytes,4,opt,name=definitions" json:"definitions,omitempty"` // network and/or token definitions
ShowMessageHash []byte `protobuf:"bytes,5,opt,name=show_message_hash,json=showMessageHash" json:"show_message_hash,omitempty"` // hash of the typed data to be signed (if set, user will be asked to confirm before signing)
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
// Default values for EthereumSignTypedData fields.
const (
Default_EthereumSignTypedData_MetamaskV4Compat = bool(true)
)
func (x *EthereumSignTypedData) Reset() {
*x = EthereumSignTypedData{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumSignTypedData) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumSignTypedData) ProtoMessage() {}
func (x *EthereumSignTypedData) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[0]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumSignTypedData.ProtoReflect.Descriptor instead.
func (*EthereumSignTypedData) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{0}
}
func (x *EthereumSignTypedData) GetAddressN() []uint32 {
if x != nil {
return x.AddressN
}
return nil
}
func (x *EthereumSignTypedData) GetPrimaryType() string {
if x != nil && x.PrimaryType != nil {
return *x.PrimaryType
}
return ""
}
func (x *EthereumSignTypedData) GetMetamaskV4Compat() bool {
if x != nil && x.MetamaskV4Compat != nil {
return *x.MetamaskV4Compat
}
return Default_EthereumSignTypedData_MetamaskV4Compat
}
func (x *EthereumSignTypedData) GetDefinitions() *EthereumDefinitions {
if x != nil {
return x.Definitions
}
return nil
}
func (x *EthereumSignTypedData) GetShowMessageHash() []byte {
if x != nil {
return x.ShowMessageHash
}
return nil
}
// *
// Response: Device asks for type information about a struct.
// @next EthereumTypedDataStructAck
type EthereumTypedDataStructRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
Name *string `protobuf:"bytes,1,req,name=name" json:"name,omitempty"` // name of the requested struct
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthereumTypedDataStructRequest) Reset() {
*x = EthereumTypedDataStructRequest{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumTypedDataStructRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumTypedDataStructRequest) ProtoMessage() {}
func (x *EthereumTypedDataStructRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[1]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumTypedDataStructRequest.ProtoReflect.Descriptor instead.
func (*EthereumTypedDataStructRequest) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{1}
}
func (x *EthereumTypedDataStructRequest) GetName() string {
if x != nil && x.Name != nil {
return *x.Name
}
return ""
}
// *
// Request: Type information about a struct.
// @next EthereumTypedDataStructRequest
type EthereumTypedDataStructAck struct {
state protoimpl.MessageState `protogen:"open.v1"`
Members []*EthereumTypedDataStructAck_EthereumStructMember `protobuf:"bytes,1,rep,name=members" json:"members,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthereumTypedDataStructAck) Reset() {
*x = EthereumTypedDataStructAck{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumTypedDataStructAck) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumTypedDataStructAck) ProtoMessage() {}
func (x *EthereumTypedDataStructAck) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[2]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumTypedDataStructAck.ProtoReflect.Descriptor instead.
func (*EthereumTypedDataStructAck) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{2}
}
func (x *EthereumTypedDataStructAck) GetMembers() []*EthereumTypedDataStructAck_EthereumStructMember {
if x != nil {
return x.Members
}
return nil
}
// *
// Response: Device asks for data at the specific member path.
// @next EthereumTypedDataValueAck
type EthereumTypedDataValueRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
MemberPath []uint32 `protobuf:"varint,1,rep,name=member_path,json=memberPath" json:"member_path,omitempty"` // member path requested by device
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthereumTypedDataValueRequest) Reset() {
*x = EthereumTypedDataValueRequest{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[3]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumTypedDataValueRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumTypedDataValueRequest) ProtoMessage() {}
func (x *EthereumTypedDataValueRequest) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[3]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumTypedDataValueRequest.ProtoReflect.Descriptor instead.
func (*EthereumTypedDataValueRequest) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{3}
}
func (x *EthereumTypedDataValueRequest) GetMemberPath() []uint32 {
if x != nil {
return x.MemberPath
}
return nil
}
// *
// Request: Single value of a specific atomic field.
// @next EthereumTypedDataValueRequest
type EthereumTypedDataValueAck struct {
state protoimpl.MessageState `protogen:"open.v1"`
Value []byte `protobuf:"bytes,1,req,name=value" json:"value,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthereumTypedDataValueAck) Reset() {
*x = EthereumTypedDataValueAck{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[4]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumTypedDataValueAck) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumTypedDataValueAck) ProtoMessage() {}
func (x *EthereumTypedDataValueAck) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[4]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumTypedDataValueAck.ProtoReflect.Descriptor instead.
func (*EthereumTypedDataValueAck) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{4}
}
func (x *EthereumTypedDataValueAck) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
type EthereumTypedDataStructAck_EthereumStructMember struct {
state protoimpl.MessageState `protogen:"open.v1"`
Type *EthereumTypedDataStructAck_EthereumFieldType `protobuf:"bytes,1,req,name=type" json:"type,omitempty"`
Name *string `protobuf:"bytes,2,req,name=name" json:"name,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthereumTypedDataStructAck_EthereumStructMember) Reset() {
*x = EthereumTypedDataStructAck_EthereumStructMember{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumTypedDataStructAck_EthereumStructMember) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumTypedDataStructAck_EthereumStructMember) ProtoMessage() {}
func (x *EthereumTypedDataStructAck_EthereumStructMember) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[5]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumTypedDataStructAck_EthereumStructMember.ProtoReflect.Descriptor instead.
func (*EthereumTypedDataStructAck_EthereumStructMember) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{2, 0}
}
func (x *EthereumTypedDataStructAck_EthereumStructMember) GetType() *EthereumTypedDataStructAck_EthereumFieldType {
if x != nil {
return x.Type
}
return nil
}
func (x *EthereumTypedDataStructAck_EthereumStructMember) GetName() string {
if x != nil && x.Name != nil {
return *x.Name
}
return ""
}
type EthereumTypedDataStructAck_EthereumFieldType struct {
state protoimpl.MessageState `protogen:"open.v1"`
DataType *EthereumTypedDataStructAck_EthereumDataType `protobuf:"varint,1,req,name=data_type,json=dataType,enum=hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck_EthereumDataType" json:"data_type,omitempty"`
Size *uint32 `protobuf:"varint,2,opt,name=size" json:"size,omitempty"` // for integer types: size in bytes (uint8 has size 1, uint256 has size 32)
// for bytes types: size in bytes, or unset for dynamic
// for arrays: size in elements, or unset for dynamic
// for structs: number of members
// for string, bool and address: unset
EntryType *EthereumTypedDataStructAck_EthereumFieldType `protobuf:"bytes,3,opt,name=entry_type,json=entryType" json:"entry_type,omitempty"` // for array types, type of single entry
StructName *string `protobuf:"bytes,4,opt,name=struct_name,json=structName" json:"struct_name,omitempty"` // for structs: its name
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *EthereumTypedDataStructAck_EthereumFieldType) Reset() {
*x = EthereumTypedDataStructAck_EthereumFieldType{}
mi := &file_messages_ethereum_eip712_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *EthereumTypedDataStructAck_EthereumFieldType) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*EthereumTypedDataStructAck_EthereumFieldType) ProtoMessage() {}
func (x *EthereumTypedDataStructAck_EthereumFieldType) ProtoReflect() protoreflect.Message {
mi := &file_messages_ethereum_eip712_proto_msgTypes[6]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use EthereumTypedDataStructAck_EthereumFieldType.ProtoReflect.Descriptor instead.
func (*EthereumTypedDataStructAck_EthereumFieldType) Descriptor() ([]byte, []int) {
return file_messages_ethereum_eip712_proto_rawDescGZIP(), []int{2, 1}
}
func (x *EthereumTypedDataStructAck_EthereumFieldType) GetDataType() EthereumTypedDataStructAck_EthereumDataType {
if x != nil && x.DataType != nil {
return *x.DataType
}
return EthereumTypedDataStructAck_UINT
}
func (x *EthereumTypedDataStructAck_EthereumFieldType) GetSize() uint32 {
if x != nil && x.Size != nil {
return *x.Size
}
return 0
}
func (x *EthereumTypedDataStructAck_EthereumFieldType) GetEntryType() *EthereumTypedDataStructAck_EthereumFieldType {
if x != nil {
return x.EntryType
}
return nil
}
func (x *EthereumTypedDataStructAck_EthereumFieldType) GetStructName() string {
if x != nil && x.StructName != nil {
return *x.StructName
}
return ""
}
var File_messages_ethereum_eip712_proto protoreflect.FileDescriptor
const file_messages_ethereum_eip712_proto_rawDesc = "" +
"\n" +
"\x1emessages-ethereum-eip712.proto\x12\"hw.trezor.messages.ethereum_eip712\x1a\x17messages-ethereum.proto\"\x8b\x02\n" +
"\x15EthereumSignTypedData\x12\x1b\n" +
"\taddress_n\x18\x01 \x03(\rR\baddressN\x12!\n" +
"\fprimary_type\x18\x02 \x02(\tR\vprimaryType\x122\n" +
"\x12metamask_v4_compat\x18\x03 \x01(\b:\x04trueR\x10metamaskV4Compat\x12R\n" +
"\vdefinitions\x18\x04 \x01(\v20.hw.trezor.messages.ethereum.EthereumDefinitionsR\vdefinitions\x12*\n" +
"\x11show_message_hash\x18\x05 \x01(\fR\x0fshowMessageHash\"4\n" +
"\x1eEthereumTypedDataStructRequest\x12\x12\n" +
"\x04name\x18\x01 \x02(\tR\x04name\"\xb4\x05\n" +
"\x1aEthereumTypedDataStructAck\x12m\n" +
"\amembers\x18\x01 \x03(\v2S.hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumStructMemberR\amembers\x1a\x90\x01\n" +
"\x14EthereumStructMember\x12d\n" +
"\x04type\x18\x01 \x02(\v2P.hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldTypeR\x04type\x12\x12\n" +
"\x04name\x18\x02 \x02(\tR\x04name\x1a\xa7\x02\n" +
"\x11EthereumFieldType\x12l\n" +
"\tdata_type\x18\x01 \x02(\x0e2O.hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumDataTypeR\bdataType\x12\x12\n" +
"\x04size\x18\x02 \x01(\rR\x04size\x12o\n" +
"\n" +
"entry_type\x18\x03 \x01(\v2P.hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldTypeR\tentryType\x12\x1f\n" +
"\vstruct_name\x18\x04 \x01(\tR\n" +
"structName\"j\n" +
"\x10EthereumDataType\x12\b\n" +
"\x04UINT\x10\x01\x12\a\n" +
"\x03INT\x10\x02\x12\t\n" +
"\x05BYTES\x10\x03\x12\n" +
"\n" +
"\x06STRING\x10\x04\x12\b\n" +
"\x04BOOL\x10\x05\x12\v\n" +
"\aADDRESS\x10\x06\x12\t\n" +
"\x05ARRAY\x10\a\x12\n" +
"\n" +
"\x06STRUCT\x10\b\"@\n" +
"\x1dEthereumTypedDataValueRequest\x12\x1f\n" +
"\vmember_path\x18\x01 \x03(\rR\n" +
"memberPath\"1\n" +
"\x19EthereumTypedDataValueAck\x12\x14\n" +
"\x05value\x18\x01 \x02(\fR\x05valueB}\n" +
"#com.satoshilabs.trezor.lib.protobufB\x1bTrezorMessageEthereumEIP712Z9github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
var (
file_messages_ethereum_eip712_proto_rawDescOnce sync.Once
file_messages_ethereum_eip712_proto_rawDescData []byte
)
func file_messages_ethereum_eip712_proto_rawDescGZIP() []byte {
file_messages_ethereum_eip712_proto_rawDescOnce.Do(func() {
file_messages_ethereum_eip712_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_messages_ethereum_eip712_proto_rawDesc), len(file_messages_ethereum_eip712_proto_rawDesc)))
})
return file_messages_ethereum_eip712_proto_rawDescData
}
var file_messages_ethereum_eip712_proto_enumTypes = make([]protoimpl.EnumInfo, 1)
var file_messages_ethereum_eip712_proto_msgTypes = make([]protoimpl.MessageInfo, 7)
var file_messages_ethereum_eip712_proto_goTypes = []any{
(EthereumTypedDataStructAck_EthereumDataType)(0), // 0: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumDataType
(*EthereumSignTypedData)(nil), // 1: hw.trezor.messages.ethereum_eip712.EthereumSignTypedData
(*EthereumTypedDataStructRequest)(nil), // 2: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructRequest
(*EthereumTypedDataStructAck)(nil), // 3: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck
(*EthereumTypedDataValueRequest)(nil), // 4: hw.trezor.messages.ethereum_eip712.EthereumTypedDataValueRequest
(*EthereumTypedDataValueAck)(nil), // 5: hw.trezor.messages.ethereum_eip712.EthereumTypedDataValueAck
(*EthereumTypedDataStructAck_EthereumStructMember)(nil), // 6: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumStructMember
(*EthereumTypedDataStructAck_EthereumFieldType)(nil), // 7: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldType
(*EthereumDefinitions)(nil), // 8: hw.trezor.messages.ethereum.EthereumDefinitions
}
var file_messages_ethereum_eip712_proto_depIdxs = []int32{
8, // 0: hw.trezor.messages.ethereum_eip712.EthereumSignTypedData.definitions:type_name -> hw.trezor.messages.ethereum.EthereumDefinitions
6, // 1: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.members:type_name -> hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumStructMember
7, // 2: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumStructMember.type:type_name -> hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldType
0, // 3: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldType.data_type:type_name -> hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumDataType
7, // 4: hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldType.entry_type:type_name -> hw.trezor.messages.ethereum_eip712.EthereumTypedDataStructAck.EthereumFieldType
5, // [5:5] is the sub-list for method output_type
5, // [5:5] is the sub-list for method input_type
5, // [5:5] is the sub-list for extension type_name
5, // [5:5] is the sub-list for extension extendee
0, // [0:5] is the sub-list for field type_name
}
func init() { file_messages_ethereum_eip712_proto_init() }
func file_messages_ethereum_eip712_proto_init() {
if File_messages_ethereum_eip712_proto != nil {
return
}
file_messages_ethereum_proto_init()
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_messages_ethereum_eip712_proto_rawDesc), len(file_messages_ethereum_eip712_proto_rawDesc)),
NumEnums: 1,
NumMessages: 7,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_messages_ethereum_eip712_proto_goTypes,
DependencyIndexes: file_messages_ethereum_eip712_proto_depIdxs,
EnumInfos: file_messages_ethereum_eip712_proto_enumTypes,
MessageInfos: file_messages_ethereum_eip712_proto_msgTypes,
}.Build()
File_messages_ethereum_eip712_proto = out.File
file_messages_ethereum_eip712_proto_goTypes = nil
file_messages_ethereum_eip712_proto_depIdxs = nil
}

View file

@ -0,0 +1,99 @@
// This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum-eip712.proto
// dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
syntax = "proto2";
package hw.trezor.messages.ethereum_eip712;
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 = "TrezorMessageEthereumEIP712";
import "messages-ethereum.proto";
// Separated from messages-ethereum.proto as it is not implemented on T1 side
// and defining all the messages and fields could be even impossible as recursive
// messages are used here
/**
* Request: Ask device to sign typed data
* @start
* @next EthereumTypedDataStructRequest
* @next EthereumTypedDataValueRequest
* @next EthereumTypedDataSignature
* @next Failure
*/
message EthereumSignTypedData {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
required string primary_type = 2; // name of the root message struct
optional bool metamask_v4_compat = 3 [default=true]; // use MetaMask v4 (see https://github.com/MetaMask/eth-sig-util/issues/106)
optional ethereum.EthereumDefinitions definitions = 4; // network and/or token definitions
optional bytes show_message_hash = 5; // hash of the typed data to be signed (if set, user will be asked to confirm before signing)
}
/**
* Response: Device asks for type information about a struct.
* @next EthereumTypedDataStructAck
*/
message EthereumTypedDataStructRequest {
required string name = 1; // name of the requested struct
}
/**
* Request: Type information about a struct.
* @next EthereumTypedDataStructRequest
*/
message EthereumTypedDataStructAck {
repeated EthereumStructMember members = 1;
message EthereumStructMember {
required EthereumFieldType type = 1;
required string name = 2;
}
message EthereumFieldType {
required EthereumDataType data_type = 1;
optional uint32 size = 2; // for integer types: size in bytes (uint8 has size 1, uint256 has size 32)
// for bytes types: size in bytes, or unset for dynamic
// for arrays: size in elements, or unset for dynamic
// for structs: number of members
// for string, bool and address: unset
optional EthereumFieldType entry_type = 3; // for array types, type of single entry
optional string struct_name = 4; // for structs: its name
}
enum EthereumDataType {
UINT = 1;
INT = 2;
BYTES = 3;
STRING = 4;
BOOL = 5;
ADDRESS = 6;
ARRAY = 7;
STRUCT = 8;
}
}
/**
* Response: Device asks for data at the specific member path.
* @next EthereumTypedDataValueAck
*/
message EthereumTypedDataValueRequest {
repeated uint32 member_path = 1; // member path requested by device
}
/**
* Request: Single value of a specific atomic field.
* @next EthereumTypedDataValueRequest
*/
message EthereumTypedDataValueAck {
required bytes value = 1;
// * atomic types: value of the member.
// Length must match the `size` of the corresponding field type, unless the size is dynamic.
// * array types: number of elements, encoded as uint16.
// * struct types: undefined, Trezor will not query a struct field.
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
// This file originates from the SatoshiLabs Trezor `common` repository at: // This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto // https://github.com/trezor/trezor-common/blob/master/protob/messages-ethereum.proto
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. // dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
syntax = "proto2"; syntax = "proto2";
package hw.trezor.messages.ethereum; package hw.trezor.messages.ethereum;
@ -13,7 +13,6 @@ option java_outer_classname = "TrezorMessageEthereum";
import "messages-common.proto"; import "messages-common.proto";
/** /**
* Request: Ask device for public key corresponding to address_n path * Request: Ask device for public key corresponding to address_n path
* @start * @start
@ -30,8 +29,8 @@ message EthereumGetPublicKey {
* @end * @end
*/ */
message EthereumPublicKey { message EthereumPublicKey {
optional hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node required hw.trezor.messages.common.HDNodeType node = 1; // BIP32 public node
optional string xpub = 2; // serialized form of public node required string xpub = 2; // serialized form of public node
} }
/** /**
@ -43,6 +42,8 @@ message EthereumPublicKey {
message EthereumGetAddress { message EthereumGetAddress {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node 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 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,13 +51,14 @@ message EthereumGetAddress {
* @end * @end
*/ */
message EthereumAddress { message EthereumAddress {
optional bytes addressBin = 1; // Ethereum address as 20 bytes (legacy firmwares) optional bytes _old_address = 1 [deprecated=true]; // trezor <1.8.0, <2.1.0 - raw bytes of Ethereum address
optional string addressHex = 2; // Ethereum address as hex string (newer firmwares) optional string address = 2; // Ethereum address as hex-encoded string
} }
/** /**
* Request: Ask device to sign transaction * 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. * Note: the first at most 1024 bytes of data MUST be transmitted as part of this message.
* @start * @start
* @next EthereumTxRequest * @next EthereumTxRequest
@ -64,16 +66,45 @@ message EthereumAddress {
*/ */
message EthereumSignTx { message EthereumSignTx {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node 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 nonce = 2 [default='']; // <=256 bit unsigned big endian
optional bytes gas_price = 3; // <=256 bit unsigned big endian (in wei) required bytes gas_price = 3; // <=256 bit unsigned big endian (in wei)
optional bytes gas_limit = 4; // <=256 bit unsigned big endian required bytes gas_limit = 4; // <=256 bit unsigned big endian
optional bytes toBin = 5; // recipient address (20 bytes, legacy firmware) optional string to = 11 [default='']; // recipient address
optional string toHex = 11; // recipient address (hex string, newer firmware) optional bytes value = 6 [default='']; // <=256 bit unsigned big endian (in wei)
optional bytes value = 6; // <=256 bit unsigned big endian (in wei) optional bytes data_initial_chunk = 7 [default='']; // The initial data chunk (<= 1024 bytes)
optional bytes data_initial_chunk = 7; // The initial data chunk (<= 1024 bytes) optional uint32 data_length = 8 [default=0]; // Length of transaction payload
optional uint32 data_length = 8; // Length of transaction payload required uint64 chain_id = 9; // Chain Id for EIP 155
optional uint32 chain_id = 9; // Chain Id for EIP 155 optional uint32 tx_type = 10; // Used for Wanchain
optional uint32 tx_type = 10; // (only 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
}
/**
* 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
message EthereumAccessList {
required string address = 1;
repeated bytes storage_keys = 2;
}
} }
/** /**
@ -95,7 +126,7 @@ message EthereumTxRequest {
* @next EthereumTxRequest * @next EthereumTxRequest
*/ */
message EthereumTxAck { message EthereumTxAck {
optional bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes) required bytes data_chunk = 1; // Bytes from transaction payload (<= 1024 bytes)
} }
/** /**
@ -106,7 +137,9 @@ message EthereumTxAck {
*/ */
message EthereumSignMessage { message EthereumSignMessage {
repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node repeated uint32 address_n = 1; // BIP-32 path to derive the key from master node
optional bytes message = 2; // message to be signed 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 +147,8 @@ message EthereumSignMessage {
* @end * @end
*/ */
message EthereumMessageSignature { message EthereumMessageSignature {
optional bytes addressBin = 1; // address used to sign the message (20 bytes, legacy firmware) required bytes signature = 2; // signature of the message
optional bytes signature = 2; // signature of the message required string address = 3; // address used to sign the message
optional string addressHex = 3; // address used to sign the message (hex string, newer firmware)
} }
/** /**
@ -126,8 +158,39 @@ message EthereumMessageSignature {
* @next Failure * @next Failure
*/ */
message EthereumVerifyMessage { message EthereumVerifyMessage {
optional bytes addressBin = 1; // address to verify (20 bytes, legacy firmware) required bytes signature = 2; // signature to verify
optional bytes signature = 2; // signature to verify required bytes message = 3; // message to verify
optional bytes message = 3; // message to verify required string address = 4; // address to verify
optional string addressHex = 4; // address to verify (hex string, newer firmware) 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
} }

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
// This file originates from the SatoshiLabs Trezor `common` repository at: // This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/messages-management.proto // https://github.com/trezor/trezor-common/blob/master/protob/messages-management.proto
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. // dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
syntax = "proto2"; syntax = "proto2";
package hw.trezor.messages.management; package hw.trezor.messages.management;
@ -11,7 +11,49 @@ option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
option java_package = "com.satoshilabs.trezor.lib.protobuf"; option java_package = "com.satoshilabs.trezor.lib.protobuf";
option java_outer_classname = "TrezorMessageManagement"; option java_outer_classname = "TrezorMessageManagement";
import "messages-common.proto"; import "options.proto";
option (include_in_bitcoin_only) = true;
/**
* Type of the mnemonic backup given/received by the device during reset/recovery.
*/
enum BackupType {
Bip39 = 0; // also called "Single Backup", see BIP-0039
Slip39_Basic = 1; // also called "Shamir Backup", see SLIP-0039
Slip39_Advanced = 2; // also called "Super Shamir" or "Shamir with Groups", see SLIP-0039#two-level-scheme
Slip39_Single_Extendable = 3; // extendable single-share Shamir backup
Slip39_Basic_Extendable = 4; // extendable multi-share Shamir backup
Slip39_Advanced_Extendable = 5; // extendable multi-share Shamir backup with groups
}
/**
* Level of safety checks for unsafe actions like spending from invalid path namespace or setting high transaction fee.
*/
enum SafetyCheckLevel {
Strict = 0; // disallow unsafe actions, this is the default
PromptAlways = 1; // ask user before unsafe action
PromptTemporarily = 2; // like PromptAlways but reverts to Strict after reboot
}
/**
* Allowed display rotation angles (in degrees from North)
*/
enum DisplayRotation {
North = 0;
East = 90;
South = 180;
West = 270;
}
/**
* Format of the homescreen image
*/
enum HomescreenFormat {
Toif = 1; // full-color toif
Jpeg = 2; // jpeg
ToiG = 3; // greyscale toif
}
/** /**
* Request: Reset device to default state and ask for device details * Request: Reset device to default state and ask for device details
@ -19,8 +61,9 @@ import "messages-common.proto";
* @next Features * @next Features
*/ */
message Initialize { message Initialize {
optional bytes state = 1; // assumed device state, clear session if set and different optional bytes session_id = 1; // assumed device session id; Trezor clears caches if it is different or empty
optional bool skip_passphrase = 2; // this session should always assume empty passphrase optional bool _skip_passphrase = 2 [deprecated=true]; // removed as part of passphrase redesign
optional bool derive_cardano = 3; // whether to derive Cardano Icarus root keys in this session
} }
/** /**
@ -37,9 +80,9 @@ message GetFeatures {
*/ */
message Features { message Features {
optional string vendor = 1; // name of the manufacturer, e.g. "trezor.io" optional string vendor = 1; // name of the manufacturer, e.g. "trezor.io"
optional uint32 major_version = 2; // major version of the firmware/bootloader, e.g. 1 required uint32 major_version = 2; // major version of the firmware/bootloader, e.g. 1
optional uint32 minor_version = 3; // minor version of the firmware/bootloader, e.g. 0 required uint32 minor_version = 3; // minor version of the firmware/bootloader, e.g. 0
optional uint32 patch_version = 4; // patch version of the firmware/bootloader, e.g. 0 required uint32 patch_version = 4; // patch version of the firmware/bootloader, e.g. 0
optional bool bootloader_mode = 5; // is device in bootloader mode? optional bool bootloader_mode = 5; // is device in bootloader mode?
optional string device_id = 6; // device's unique identifier optional string device_id = 6; // device's unique identifier
optional bool pin_protection = 7; // is device protected by PIN? optional bool pin_protection = 7; // is device protected by PIN?
@ -50,51 +93,179 @@ message Features {
optional bytes revision = 13; // SCM revision of firmware optional bytes revision = 13; // SCM revision of firmware
optional bytes bootloader_hash = 14; // hash of the bootloader optional bytes bootloader_hash = 14; // hash of the bootloader
optional bool imported = 15; // was storage imported from an external source? optional bool imported = 15; // was storage imported from an external source?
optional bool pin_cached = 16; // is PIN already cached in session? optional bool unlocked = 16; // is the device unlocked? called "pin_cached" previously
optional bool passphrase_cached = 17; // is passphrase already cached in session? optional bool _passphrase_cached = 17 [deprecated=true]; // is passphrase already cached in session?
optional bool firmware_present = 18; // is valid firmware loaded? optional bool firmware_present = 18; // is valid firmware loaded?
optional bool needs_backup = 19; // does storage need backup? (equals to Storage.needs_backup) optional BackupAvailability backup_availability = 19; // does storage need backup? is repeated backup unlocked?
optional uint32 flags = 20; // device flags (equals to Storage.flags) optional uint32 flags = 20; // device flags (equals to Storage.flags)
optional string model = 21; // device hardware model optional string model = 21; // device hardware model
optional uint32 fw_major = 22; // reported firmware version if in bootloader mode optional uint32 fw_major = 22; // reported firmware version if in bootloader mode
optional uint32 fw_minor = 23; // reported firmware version if in bootloader mode optional uint32 fw_minor = 23; // reported firmware version if in bootloader mode
optional uint32 fw_patch = 24; // reported firmware version if in bootloader mode optional uint32 fw_patch = 24; // reported firmware version if in bootloader mode
optional string fw_vendor = 25; // reported firmware vendor if in bootloader mode optional string fw_vendor = 25; // reported firmware vendor if in bootloader mode
optional bytes fw_vendor_keys = 26; // reported firmware vendor keys (their hash) // optional bytes fw_vendor_keys = 26; // obsoleted, use fw_vendor
optional bool unfinished_backup = 27; // report unfinished backup (equals to Storage.unfinished_backup) optional bool unfinished_backup = 27; // report unfinished backup (equals to Storage.unfinished_backup)
optional bool no_backup = 28; // report no backup (equals to Storage.no_backup) optional bool no_backup = 28; // report no backup (equals to Storage.no_backup)
optional RecoveryStatus recovery_status = 29; // whether or not we are in recovery mode and of what kind
repeated Capability capabilities = 30; // list of supported capabilities
optional BackupType backup_type = 31; // type of device backup (BIP-39 / SLIP-39 basic / SLIP-39 advanced)
optional bool sd_card_present = 32; // is SD card present
optional bool sd_protection = 33; // is SD Protect enabled
optional bool wipe_code_protection = 34; // is wipe code protection enabled
optional bytes session_id = 35;
optional bool passphrase_always_on_device = 36; // device enforces passphrase entry on Trezor
optional SafetyCheckLevel safety_checks = 37; // safety check level, set to Prompt to limit path namespace enforcement
optional uint32 auto_lock_delay_ms = 38; // number of milliseconds after which the device locks itself
optional DisplayRotation display_rotation = 39; // rotation of display (in degrees from North)
optional bool experimental_features = 40; // are experimental message types enabled?
optional bool busy = 41; // is the device busy, showing "Do not disconnect"?
optional HomescreenFormat homescreen_format = 42; // format of the homescreen, 1 = TOIf, 2 = jpg, 3 = TOIG
optional bool hide_passphrase_from_host = 43; // should we hide the passphrase when it comes from host?
optional string internal_model = 44; // internal model name
optional uint32 unit_color = 45; // color of the unit/device
optional bool unit_btconly = 46; // unit/device is intended as bitcoin only
optional uint32 homescreen_width = 47; // homescreen width in pixels
optional uint32 homescreen_height = 48; // homescreen height in pixels
optional bool bootloader_locked = 49; // bootloader is locked
optional bool language_version_matches = 50 [default=true]; // translation blob version matches firmware version
optional uint32 unit_packaging = 51; // unit/device packaging version
optional bool haptic_feedback = 52; // haptic feedback is enabled
optional RecoveryType recovery_type = 53; // what type of recovery we are in. NB: this works in conjunction with recovery_status
optional uint32 optiga_sec = 54; // Optiga's security event counter.
enum BackupAvailability {
/// Device is already backed up, or a previous backup has failed.
NotAvailable = 0;
/// Device is not backed up. Backup is required.
Required = 1;
/// Device is already backed up and can be backed up again.
Available = 2;
}
enum RecoveryStatus {
Nothing = 0; // we are not in recovery mode
Recovery = 1; // we are in "Normal" or "DryRun" recovery
Backup = 2; // we are in repeated backup mode
}
enum Capability {
option (has_bitcoin_only_values) = true;
Capability_Bitcoin = 1 [(bitcoin_only) = true];
Capability_Bitcoin_like = 2; // Altcoins based on the Bitcoin source code
Capability_Binance = 3; // BNB Smart Chain
Capability_Cardano = 4;
Capability_Crypto = 5 [(bitcoin_only) = true]; // generic crypto operations for GPG, SSH, etc.
Capability_EOS = 6;
Capability_Ethereum = 7;
Capability_Lisk = 8 [deprecated = true];
Capability_Monero = 9;
Capability_NEM = 10;
Capability_Ripple = 11;
Capability_Stellar = 12;
Capability_Tezos = 13;
Capability_U2F = 14;
Capability_Shamir = 15 [(bitcoin_only) = true];
Capability_ShamirGroups = 16 [(bitcoin_only) = true];
Capability_PassphraseEntry = 17 [(bitcoin_only) = true]; // the device is capable of passphrase entry directly on the device
Capability_Solana = 18;
Capability_Translations = 19 [(bitcoin_only) = true];
Capability_Brightness = 20 [(bitcoin_only) = true];
Capability_Haptic = 21 [(bitcoin_only) = true];
Capability_BLE = 22 [(bitcoin_only) = true]; // Bluetooth Low Energy
Capability_NFC = 23 [(bitcoin_only) = true]; // Near Field Communications
}
} }
/** /**
* Request: clear session (removes cached PIN, passphrase, etc). * Request: soft-lock the device. Following actions will require PIN. Passphrases remain cached.
* @start * @start
* @next Success * @next Success
*/ */
message ClearSession { message LockDevice {
} }
/** /**
* Request: change language and/or label of the device * Request: Show a "Do not disconnect" dialog instead of the standard homescreen.
* @start * @start
* @next Success * @next Success
*/
message SetBusy {
optional uint32 expiry_ms = 1; // The time in milliseconds after which the dialog will automatically disappear. Overrides any previously set expiry. If not set, then the dialog is hidden.
}
/**
* Request: end the current sesson. Following actions must call Initialize again.
* Cache for the current session is discarded, other sessions remain intact.
* Device is not PIN-locked.
* @start
* @next Success
*/
message EndSession {
}
/**
* Request: change some property of the device, e.g. label or homescreen
* @start
* @next Success
* @next DataChunkRequest
* @next Failure * @next Failure
*/ */
message ApplySettings { message ApplySettings {
optional string language = 1; optional string language = 1 [deprecated=true];
optional string label = 2; optional string label = 2;
optional bool use_passphrase = 3; optional bool use_passphrase = 3;
optional bytes homescreen = 4; optional bytes homescreen = 4; // homescreen image in single array, deprecated for 14
optional PassphraseSourceType passphrase_source = 5; optional uint32 _passphrase_source = 5 [deprecated=true]; // ASK = 0; DEVICE = 1; HOST = 2;
optional uint32 auto_lock_delay_ms = 6; optional uint32 auto_lock_delay_ms = 6;
optional uint32 display_rotation = 7; // in degrees from North optional DisplayRotation display_rotation = 7; // rotation of display (in degrees from North)
/** optional bool passphrase_always_on_device = 8; // do not prompt for passphrase, enforce device entry
* Structure representing passphrase source optional SafetyCheckLevel safety_checks = 9; // Safety check level, set to Prompt to limit path namespace enforcement
*/ optional bool experimental_features = 10; // enable experimental message types
enum PassphraseSourceType { optional bool hide_passphrase_from_host = 11; // do not show passphrase coming from host
ASK = 0; optional bool haptic_feedback = 13; // enable haptic feedback
DEVICE = 1; optional uint32 homescreen_length = 14; // byte length of new homescreen, device will request chunks
HOST = 2;
} }
/**
* Request: change the device language via translation data.
* Does not send the translation data itself, as they are too large for one message.
* Device will request the translation data in chunks.
* @start
* @next DataChunkRequest
* @next Failure
*/
message ChangeLanguage {
// byte length of the whole translation blob (set to 0 for default language - english)
required uint32 data_length = 1;
// Prompt the user on screen.
// In certain conditions (such as freshly installed device), the confirmation prompt
// is not mandatory. Setting show_display=false will skip the prompt if that's
// the case. If the device does not allow skipping the prompt, a request with
// show_display=false will return a failure. (This way the host can safely try
// to change the language without invoking a prompt.)
// Setting show_display to true will always show the prompt.
// Leaving the option unset will show the prompt only when necessary.
optional bool show_display = 2;
}
/**
* Response: Device asks for more data from translation/homescreen image.
* @end
* @next DataChunkAck
*/
message DataChunkRequest {
required uint32 data_length = 1; // Number of bytes being requested
required uint32 data_offset = 2; // Offset of the first byte being requested
}
/**
* Request: Translation/homescreen payload data.
* @next DataChunkRequest
* @next Success
*/
message DataChunkAck {
required bytes data_chunk = 1; // Bytes from translation/homescreen payload
} }
/** /**
@ -104,7 +275,7 @@ message ApplySettings {
* @next Failure * @next Failure
*/ */
message ApplyFlags { message ApplyFlags {
optional uint32 flags = 1; // bitmask, can only set bits, not unset required uint32 flags = 1; // bitmask, can only set bits, not unset
} }
/** /**
@ -117,16 +288,42 @@ message ChangePin {
optional bool remove = 1; // is PIN removal requested? optional bool remove = 1; // is PIN removal requested?
} }
/**
* Request: Starts workflow for setting/removing the wipe code
* @start
* @next Success
* @next Failure
*/
message ChangeWipeCode {
optional bool remove = 1; // is wipe code removal requested?
}
/**
* Request: Starts workflow for enabling/regenerating/disabling SD card protection
* @start
* @next Success
* @next Failure
*/
message SdProtect {
required SdProtectOperationType operation = 1;
/**
* Structure representing SD card protection operation
*/
enum SdProtectOperationType {
DISABLE = 0;
ENABLE = 1;
REFRESH = 2;
}
}
/** /**
* Request: Test if the device is alive, device sends back the message in Success response * Request: Test if the device is alive, device sends back the message in Success response
* @start * @start
* @next Success * @next Success
*/ */
message Ping { message Ping {
optional string message = 1; // message to send back in Success message optional string message = 1 [default=""]; // message to send back in Success message
optional bool button_protection = 2; // ask for button press optional bool button_protection = 2; // ask for button press
optional bool pin_protection = 3; // ask for PIN if set in device
optional bool passphrase_protection = 4; // ask for passphrase if set in device
} }
/** /**
@ -155,6 +352,43 @@ message Entropy {
required bytes entropy = 1; // chunk of random generated bytes required bytes entropy = 1; // chunk of random generated bytes
} }
/**
* Request: Get a hash of the installed firmware combined with an optional challenge.
* @start
* @next FirmwareHash
* @next Failure
*/
message GetFirmwareHash {
optional bytes challenge = 1; // Blake2s key up to 32 bytes in length.
}
/**
* Response: Hash of the installed firmware combined with the optional challenge.
* @end
*/
message FirmwareHash {
required bytes hash = 1;
}
/**
* Request: Request a signature of the provided challenge.
* @start
* @next AuthenticityProof
* @next Failure
*/
message AuthenticateDevice {
required bytes challenge = 1; // A random challenge to sign.
}
/**
* Response: Signature of the provided challenge along with a certificate issued by the Trezor company.
* @end
*/
message AuthenticityProof {
repeated bytes certificates = 1; // A certificate chain starting with the device certificate, followed by intermediate CA certificates, the last of which is signed by Trezor company's root CA.
required bytes signature = 2; // A DER-encoded signature of "\0x13AuthenticateDevice:" + length-prefixed challenge that should be verified using the device certificate.
}
/** /**
* Request: Request device to wipe all sensitive data and settings * Request: Request device to wipe all sensitive data and settings
* @start * @start
@ -171,14 +405,15 @@ message WipeDevice {
* @next Failure * @next Failure
*/ */
message LoadDevice { message LoadDevice {
optional string mnemonic = 1; // seed encoded as BIP-39 mnemonic (12, 18 or 24 words) repeated string mnemonics = 1; // seed encoded as mnemonic (12, 18 or 24 words for BIP39, 20 or 33 for SLIP39)
optional hw.trezor.messages.common.HDNodeType node = 2; // BIP-32 node
optional string pin = 3; // set PIN protection optional string pin = 3; // set PIN protection
optional bool passphrase_protection = 4; // enable master node encryption using passphrase optional bool passphrase_protection = 4; // enable master node encryption using passphrase
optional string language = 5 [default='english']; // device language optional string language = 5 [deprecated=true]; // deprecated (use ChangeLanguage)
optional string label = 6; // device label optional string label = 6; // device label
optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum optional bool skip_checksum = 7; // do not test mnemonic for valid BIP-39 checksum
optional uint32 u2f_counter = 8; // U2F counter optional uint32 u2f_counter = 8; // U2F counter
optional bool needs_backup = 9; // set "needs backup" flag
optional bool no_backup = 10; // indicate that no backup is going to be made
} }
/** /**
@ -188,15 +423,17 @@ message LoadDevice {
* @next Failure * @next Failure
*/ */
message ResetDevice { message ResetDevice {
optional bool display_random = 1; // display entropy generated by the device before asking for additional entropy reserved 1; // unused display_random
optional uint32 strength = 2 [default=256]; // strength of seed in bits optional uint32 strength = 2 [default=256]; // strength of seed in bits
optional bool passphrase_protection = 3; // enable master node encryption using passphrase optional bool passphrase_protection = 3; // enable master node encryption using passphrase
optional bool pin_protection = 4; // enable PIN protection optional bool pin_protection = 4; // enable PIN protection
optional string language = 5 [default='english']; // device language optional string language = 5 [deprecated=true]; // deprecated (use ChangeLanguage)
optional string label = 6; // device label optional string label = 6; // device label
optional uint32 u2f_counter = 7; // U2F counter optional uint32 u2f_counter = 7; // U2F counter
optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow optional bool skip_backup = 8; // postpone seed backup to BackupDevice workflow
optional bool no_backup = 9; // indicate that no backup is going to be made optional bool no_backup = 9; // indicate that no backup is going to be made
optional BackupType backup_type = 10 [default=Bip39]; // type of the mnemonic backup
optional bool entropy_check = 11; // run with entropy check protocol
} }
/** /**
@ -205,6 +442,12 @@ message ResetDevice {
* @next Success * @next Success
*/ */
message BackupDevice { message BackupDevice {
optional uint32 group_threshold = 1;
message Slip39Group {
required uint32 member_threshold = 1;
required uint32 member_count = 2;
}
repeated Slip39Group groups = 2;
} }
/** /**
@ -212,14 +455,34 @@ message BackupDevice {
* @next EntropyAck * @next EntropyAck
*/ */
message EntropyRequest { message EntropyRequest {
optional bytes entropy_commitment = 1; // HMAC-SHA256 of Trezor's internal entropy used in entropy check.
optional bytes prev_entropy = 2; // Trezor's internal entropy from the previous round of entropy check.
} }
/** /**
* Request: Provide additional entropy for seed generation function * Request: Provide additional entropy for seed generation function
* @next Success * @next Success
* @next EntropyCheckReady
*/ */
message EntropyAck { message EntropyAck {
optional bytes entropy = 1; // 256 bits (32 bytes) of random data required bytes entropy = 1; // 256 bits (32 bytes) of the host's random data
}
/**
* Response: Trezor is ready for the next phase of the entropy check protocol.
* @next EntropyCheckContinue
* @next GetPublicKey
*/
message EntropyCheckReady {
}
/**
* Request: Proceed with the next phase of the entropy check protocol, asking Trezor to either reveal its internal entropy or to finish and store the seed.
* @next Success
* @next EntropyRequest
*/
message EntropyCheckContinue {
optional bool finish = 1 [default=false]; // finish the entropy check protocol, store the seed
} }
/** /**
@ -229,38 +492,44 @@ message EntropyAck {
* @next WordRequest * @next WordRequest
*/ */
message RecoveryDevice { message RecoveryDevice {
optional uint32 word_count = 1; // number of words in BIP-39 mnemonic optional uint32 word_count = 1; // number of words in BIP-39 mnemonic (T1 only)
optional bool passphrase_protection = 2; // enable master node encryption using passphrase optional bool passphrase_protection = 2; // enable master node encryption using passphrase
optional bool pin_protection = 3; // enable PIN protection optional bool pin_protection = 3; // enable PIN protection
optional string language = 4 [default='english']; // device language optional string language = 4 [deprecated=true]; // deprecated (use ChangeLanguage)
optional string label = 5; // device label optional string label = 5; // device label
optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process optional bool enforce_wordlist = 6; // enforce BIP-39 wordlist during the process (T1 only)
// 7 reserved for unused recovery method reserved 7; // unused recovery method
optional RecoveryDeviceType type = 8; // supported recovery type optional RecoveryDeviceInputMethod input_method = 8; // supported recovery input method (T1 only)
optional uint32 u2f_counter = 9; // U2F counter optional uint32 u2f_counter = 9; // U2F counter
optional bool dry_run = 10; // perform dry-run recovery workflow (for safe mnemonic validation) optional RecoveryType type = 10 [default=NormalRecovery]; // the type of recovery to perform
/** /**
* Type of recovery procedure. These should be used as bitmask, e.g., * Type of recovery procedure. These should be used as bitmask, e.g.,
* `RecoveryDeviceType_ScrambledWords | RecoveryDeviceType_Matrix` * `RecoveryDeviceInputMethod_ScrambledWords | RecoveryDeviceInputMethod_Matrix`
* listing every method supported by the host computer. * listing every method supported by the host computer.
* *
* Note that ScrambledWords must be supported by every implementation * Note that ScrambledWords must be supported by every implementation
* for backward compatibility; there is no way to not support it. * for backward compatibility; there is no way to not support it.
*/ */
enum RecoveryDeviceType { enum RecoveryDeviceInputMethod {
// use powers of two when extending this field // use powers of two when extending this field
RecoveryDeviceType_ScrambledWords = 0; // words in scrambled order ScrambledWords = 0; // words in scrambled order
RecoveryDeviceType_Matrix = 1; // matrix recovery type Matrix = 1; // matrix recovery type
} }
} }
enum RecoveryType {
NormalRecovery = 0; // recovery from seedphrase on an uninitialized device
DryRun = 1; // mnemonic validation
UnlockRepeatedBackup = 2; // unlock SLIP-39 repeated backup
}
/** /**
* Response: Device is waiting for user to enter word of the mnemonic * Response: Device is waiting for user to enter word of the mnemonic
* Its position is shown only on device's internal display. * Its position is shown only on device's internal display.
* @next WordAck * @next WordAck
*/ */
message WordRequest { message WordRequest {
optional WordRequestType type = 1; required WordRequestType type = 1;
/** /**
* Type of Recovery Word request * Type of Recovery Word request
*/ */
@ -287,5 +556,137 @@ message WordAck {
* @next Success * @next Success
*/ */
message SetU2FCounter { message SetU2FCounter {
optional uint32 u2f_counter = 1; // counter required uint32 u2f_counter = 1;
}
/**
* Request: Set U2F counter
* @start
* @next NextU2FCounter
*/
message GetNextU2FCounter {
}
/**
* Request: Set U2F counter
* @end
*/
message NextU2FCounter {
required uint32 u2f_counter = 1;
}
/**
* Request: Ask device to prepare for a preauthorized operation.
* @start
* @next PreauthorizedRequest
* @next Failure
*/
message DoPreauthorized {
}
/**
* Request: Device awaits a preauthorized operation.
* @start
* @next SignTx
* @next GetOwnershipProof
*/
message PreauthorizedRequest {
}
/**
* Request: Cancel any outstanding authorization in the current session.
* @start
* @next Success
* @next Failure
*/
message CancelAuthorization {
}
/**
* Request: Reboot firmware to bootloader
* @start
* @next Success
* @next DataChunkRequest
*/
message RebootToBootloader {
// Action to be performed after rebooting to bootloader
optional BootCommand boot_command = 1 [default=STOP_AND_WAIT];
// Firmware header to be flashed after rebooting to bootloader
optional bytes firmware_header = 2;
// Length of language blob to be installed before upgrading firmware
optional uint32 language_data_length = 3 [default=0];
enum BootCommand {
// Go to bootloader menu
STOP_AND_WAIT = 0;
// Connect to host and wait for firmware update
INSTALL_UPGRADE = 1;
}
}
/**
* Request: Ask device to generate a random nonce and store it in the session's cache
* @start
* @next Nonce
*/
message GetNonce {
option (experimental_message) = true;
}
/**
* Response: Contains a random nonce
* @end
*/
message Nonce {
option (experimental_message) = true;
required bytes nonce = 1; // a 32-byte random value generated by Trezor
}
/**
* Request: Ask device to unlock a subtree of the keychain.
* @start
* @next UnlockedPathRequest
* @next Failure
*/
message UnlockPath {
repeated uint32 address_n = 1; // prefix of the BIP-32 path leading to the account (m / purpose')
optional bytes mac = 2; // the MAC returned by UnlockedPathRequest
}
/**
* Request: Device awaits an operation.
* @start
* @next SignTx
* @next GetPublicKey
* @next GetAddress
*/
message UnlockedPathRequest {
required bytes mac = 1; // authentication code for future UnlockPath calls
}
/**
* Request: Show tutorial screens on the device
* @start
* @next Success
*/
message ShowDeviceTutorial {
}
/**
* Request: Unlocks bootloader, !irreversible!
* @start
* @next Success
* @next Failure
*/
message UnlockBootloader {
}
/**
* Request: Set device brightness
* @start
* @next Success
*/
message SetBrightness {
optional uint32 value = 1; // if not specified, let the user choose
} }

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
// This file originates from the SatoshiLabs Trezor `common` repository at: // This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/messages.proto // https://github.com/trezor/trezor-common/blob/master/protob/messages.proto
// dated 28.05.2019, commit 893fd219d4a01bcffa0cd9cfa631856371ec5aa9. // dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
syntax = "proto2"; syntax = "proto2";
package hw.trezor.messages; package hw.trezor.messages;
/** /**
* Messages for TREZOR communication * Messages for Trezor communication
*/ */
option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"; option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor";
@ -15,100 +15,137 @@ option go_package = "github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
option java_package = "com.satoshilabs.trezor.lib.protobuf"; option java_package = "com.satoshilabs.trezor.lib.protobuf";
option java_outer_classname = "TrezorMessage"; option java_outer_classname = "TrezorMessage";
import "options.proto";
import "google/protobuf/descriptor.proto"; option (include_in_bitcoin_only) = true;
/** /**
* Options for specifying message direction and type of wire (normal/debug) * Mapping between Trezor wire identifier (uint) and a protobuf message
*/
extend google.protobuf.EnumValueOptions {
optional bool wire_in = 50002; // message can be transmitted via wire from PC to TREZOR
optional bool wire_out = 50003; // message can be transmitted via wire from TREZOR to PC
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to TREZOR
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from TREZOR to PC
optional bool wire_tiny = 50006; // message is handled by TREZOR when the USB stack is in tiny mode
optional bool wire_bootloader = 50007; // message is only handled by TREZOR Bootloader
optional bool wire_no_fsm = 50008; // message is not handled by TREZOR unless the USB stack is in tiny mode
}
/**
* Mapping between TREZOR wire identifier (uint) and a protobuf message
*/ */
enum MessageType { enum MessageType {
option (has_bitcoin_only_values) = true;
// Management // Management
MessageType_Initialize = 0 [(wire_in) = true, (wire_tiny) = true]; MessageType_Initialize = 0 [(bitcoin_only) = true, (wire_in) = true, (wire_tiny) = true];
MessageType_Ping = 1 [(wire_in) = true]; MessageType_Ping = 1 [(bitcoin_only) = true, (wire_in) = true];
MessageType_Success = 2 [(wire_out) = true]; MessageType_Success = 2 [(bitcoin_only) = true, (wire_out) = true, (wire_debug_out) = true];
MessageType_Failure = 3 [(wire_out) = true]; MessageType_Failure = 3 [(bitcoin_only) = true, (wire_out) = true, (wire_debug_out) = true];
MessageType_ChangePin = 4 [(wire_in) = true]; MessageType_ChangePin = 4 [(bitcoin_only) = true, (wire_in) = true];
MessageType_WipeDevice = 5 [(wire_in) = true]; MessageType_WipeDevice = 5 [(bitcoin_only) = true, (wire_in) = true];
MessageType_GetEntropy = 9 [(wire_in) = true]; MessageType_GetEntropy = 9 [(bitcoin_only) = true, (wire_in) = true];
MessageType_Entropy = 10 [(wire_out) = true]; MessageType_Entropy = 10 [(bitcoin_only) = true, (wire_out) = true];
MessageType_LoadDevice = 13 [(wire_in) = true]; MessageType_LoadDevice = 13 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ResetDevice = 14 [(wire_in) = true]; MessageType_ResetDevice = 14 [(bitcoin_only) = true, (wire_in) = true];
MessageType_Features = 17 [(wire_out) = true]; MessageType_SetBusy = 16 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PinMatrixRequest = 18 [(wire_out) = true]; MessageType_Features = 17 [(bitcoin_only) = true, (wire_out) = true];
MessageType_PinMatrixAck = 19 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; MessageType_PinMatrixRequest = 18 [(bitcoin_only) = true, (wire_out) = true];
MessageType_Cancel = 20 [(wire_in) = true, (wire_tiny) = true]; MessageType_PinMatrixAck = 19 [(bitcoin_only) = true, (wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
MessageType_ClearSession = 24 [(wire_in) = true]; MessageType_Cancel = 20 [(bitcoin_only) = true, (wire_in) = true, (wire_tiny) = true];
MessageType_ApplySettings = 25 [(wire_in) = true]; MessageType_LockDevice = 24 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ButtonRequest = 26 [(wire_out) = true]; MessageType_ApplySettings = 25 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ButtonAck = 27 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; MessageType_ButtonRequest = 26 [(bitcoin_only) = true, (wire_out) = true];
MessageType_ApplyFlags = 28 [(wire_in) = true]; MessageType_ButtonAck = 27 [(bitcoin_only) = true, (wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
MessageType_BackupDevice = 34 [(wire_in) = true]; MessageType_ApplyFlags = 28 [(bitcoin_only) = true, (wire_in) = true];
MessageType_EntropyRequest = 35 [(wire_out) = true]; MessageType_GetNonce = 31 [(bitcoin_only) = true, (wire_in) = true];
MessageType_EntropyAck = 36 [(wire_in) = true]; MessageType_Nonce = 33 [(bitcoin_only) = true, (wire_out) = true];
MessageType_PassphraseRequest = 41 [(wire_out) = true]; MessageType_BackupDevice = 34 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PassphraseAck = 42 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; MessageType_EntropyRequest = 35 [(bitcoin_only) = true, (wire_out) = true];
MessageType_PassphraseStateRequest = 77 [(wire_out) = true]; MessageType_EntropyAck = 36 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PassphraseStateAck = 78 [(wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; MessageType_EntropyCheckReady = 994 [(bitcoin_only) = true, (wire_out) = true];
MessageType_RecoveryDevice = 45 [(wire_in) = true]; MessageType_EntropyCheckContinue = 995 [(bitcoin_only) = true, (wire_in) = true];
MessageType_WordRequest = 46 [(wire_out) = true]; MessageType_PassphraseRequest = 41 [(bitcoin_only) = true, (wire_out) = true];
MessageType_WordAck = 47 [(wire_in) = true]; MessageType_PassphraseAck = 42 [(bitcoin_only) = true, (wire_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
MessageType_GetFeatures = 55 [(wire_in) = true]; MessageType_RecoveryDevice = 45 [(bitcoin_only) = true, (wire_in) = true];
MessageType_WordRequest = 46 [(bitcoin_only) = true, (wire_out) = true];
MessageType_WordAck = 47 [(bitcoin_only) = true, (wire_in) = true];
MessageType_GetFeatures = 55 [(bitcoin_only) = true, (wire_in) = true];
MessageType_SdProtect = 79 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ChangeWipeCode = 82 [(bitcoin_only) = true, (wire_in) = true];
MessageType_EndSession = 83 [(bitcoin_only) = true, (wire_in) = true];
MessageType_DoPreauthorized = 84 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PreauthorizedRequest = 85 [(bitcoin_only) = true, (wire_out) = true];
MessageType_CancelAuthorization = 86 [(bitcoin_only) = true, (wire_in) = true];
MessageType_RebootToBootloader = 87 [(bitcoin_only) = true, (wire_in) = true];
MessageType_GetFirmwareHash = 88 [(bitcoin_only) = true, (wire_in) = true];
MessageType_FirmwareHash = 89 [(bitcoin_only) = true, (wire_out) = true];
reserved 90 to 92;
MessageType_UnlockPath = 93 [(bitcoin_only) = true, (wire_in) = true];
MessageType_UnlockedPathRequest = 94 [(bitcoin_only) = true, (wire_out) = true];
MessageType_ShowDeviceTutorial = 95 [(bitcoin_only) = true, (wire_in) = true];
MessageType_UnlockBootloader = 96 [(bitcoin_only) = true, (wire_in) = true];
MessageType_AuthenticateDevice = 97 [(bitcoin_only) = true, (wire_out) = true];
MessageType_AuthenticityProof = 98 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ChangeLanguage = 990 [(bitcoin_only) = true, (wire_in) = true];
MessageType_DataChunkRequest = 991 [(bitcoin_only) = true, (wire_out) = true];
MessageType_DataChunkAck = 992 [(bitcoin_only) = true, (wire_in) = true];
MessageType_SetBrightness = 993 [(bitcoin_only) = true, (wire_in) = true];
MessageType_SetU2FCounter = 63 [(wire_in) = true]; MessageType_SetU2FCounter = 63 [(wire_in) = true];
MessageType_GetNextU2FCounter = 80 [(wire_in) = true];
MessageType_NextU2FCounter = 81 [(wire_out) = true];
// Deprecated messages, kept for protobuf compatibility.
MessageType_Deprecated_PassphraseStateRequest = 77 [deprecated = true];
MessageType_Deprecated_PassphraseStateAck = 78 [deprecated = true];
// Bootloader // Bootloader
MessageType_FirmwareErase = 6 [(wire_in) = true, (wire_bootloader) = true]; MessageType_FirmwareErase = 6 [(bitcoin_only) = true, (wire_in) = true, (wire_bootloader) = true];
MessageType_FirmwareUpload = 7 [(wire_in) = true, (wire_bootloader) = true]; MessageType_FirmwareUpload = 7 [(bitcoin_only) = true, (wire_in) = true, (wire_bootloader) = true];
MessageType_FirmwareRequest = 8 [(wire_out) = true, (wire_bootloader) = true]; MessageType_FirmwareRequest = 8 [(bitcoin_only) = true, (wire_out) = true, (wire_bootloader) = true];
MessageType_SelfTest = 32 [(wire_in) = true, (wire_bootloader) = true]; MessageType_ProdTestT1 = 32 [(bitcoin_only) = true, (wire_in) = true, (wire_bootloader) = true];
// BLE
MessageType_BleUnpair = 8001 [(bitcoin_only) = true, (wire_in) = true];
// Bitcoin // Bitcoin
MessageType_GetPublicKey = 11 [(wire_in) = true]; MessageType_GetPublicKey = 11 [(bitcoin_only) = true, (wire_in) = true];
MessageType_PublicKey = 12 [(wire_out) = true]; MessageType_PublicKey = 12 [(bitcoin_only) = true, (wire_out) = true];
MessageType_SignTx = 15 [(wire_in) = true]; MessageType_SignTx = 15 [(bitcoin_only) = true, (wire_in) = true];
MessageType_TxRequest = 21 [(wire_out) = true]; MessageType_TxRequest = 21 [(bitcoin_only) = true, (wire_out) = true];
MessageType_TxAck = 22 [(wire_in) = true]; MessageType_TxAck = 22 [(bitcoin_only) = true, (wire_in) = true];
MessageType_GetAddress = 29 [(wire_in) = true]; MessageType_GetAddress = 29 [(bitcoin_only) = true, (wire_in) = true];
MessageType_Address = 30 [(wire_out) = true]; MessageType_Address = 30 [(bitcoin_only) = true, (wire_out) = true];
MessageType_SignMessage = 38 [(wire_in) = true]; MessageType_TxAckPaymentRequest = 37 [(wire_in) = true];
MessageType_VerifyMessage = 39 [(wire_in) = true]; MessageType_SignMessage = 38 [(bitcoin_only) = true, (wire_in) = true];
MessageType_MessageSignature = 40 [(wire_out) = true]; MessageType_VerifyMessage = 39 [(bitcoin_only) = true, (wire_in) = true];
MessageType_MessageSignature = 40 [(bitcoin_only) = true, (wire_out) = true];
MessageType_GetOwnershipId = 43 [(bitcoin_only) = true, (wire_in) = true];
MessageType_OwnershipId = 44 [(bitcoin_only) = true, (wire_out) = true];
MessageType_GetOwnershipProof = 49 [(bitcoin_only) = true, (wire_in) = true];
MessageType_OwnershipProof = 50 [(bitcoin_only) = true, (wire_out) = true];
MessageType_AuthorizeCoinJoin = 51 [(bitcoin_only) = true, (wire_in) = true];
// Crypto // Crypto
MessageType_CipherKeyValue = 23 [(wire_in) = true]; MessageType_CipherKeyValue = 23 [(bitcoin_only) = true, (wire_in) = true];
MessageType_CipheredKeyValue = 48 [(wire_out) = true]; MessageType_CipheredKeyValue = 48 [(bitcoin_only) = true, (wire_out) = true];
MessageType_SignIdentity = 53 [(wire_in) = true]; MessageType_SignIdentity = 53 [(bitcoin_only) = true, (wire_in) = true];
MessageType_SignedIdentity = 54 [(wire_out) = true]; MessageType_SignedIdentity = 54 [(bitcoin_only) = true, (wire_out) = true];
MessageType_GetECDHSessionKey = 61 [(wire_in) = true]; MessageType_GetECDHSessionKey = 61 [(bitcoin_only) = true, (wire_in) = true];
MessageType_ECDHSessionKey = 62 [(wire_out) = true]; MessageType_ECDHSessionKey = 62 [(bitcoin_only) = true, (wire_out) = true];
MessageType_CosiCommit = 71 [(wire_in) = true]; // dropped: CosiCommit, CosiCommitment, CosiSign, CosiSignature
MessageType_CosiCommitment = 72 [(wire_out) = true]; reserved 71 to 74;
MessageType_CosiSign = 73 [(wire_in) = true];
MessageType_CosiSignature = 74 [(wire_out) = true];
// Debug // Debug
MessageType_DebugLinkDecision = 100 [(wire_debug_in) = true, (wire_tiny) = true, (wire_no_fsm) = true]; MessageType_DebugLinkDecision = 100 [(bitcoin_only) = true, (wire_debug_in) = true, (wire_tiny) = true, (wire_no_fsm) = true];
MessageType_DebugLinkGetState = 101 [(wire_debug_in) = true, (wire_tiny) = true]; MessageType_DebugLinkGetState = 101 [(bitcoin_only) = true, (wire_debug_in) = true, (wire_tiny) = true];
MessageType_DebugLinkState = 102 [(wire_debug_out) = true]; MessageType_DebugLinkState = 102 [(bitcoin_only) = true, (wire_debug_out) = true];
MessageType_DebugLinkStop = 103 [(wire_debug_in) = true]; MessageType_DebugLinkStop = 103 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkLog = 104 [(wire_debug_out) = true]; MessageType_DebugLinkLog = 104 [(bitcoin_only) = true, (wire_debug_out) = true];
MessageType_DebugLinkMemoryRead = 110 [(wire_debug_in) = true]; MessageType_DebugLinkMemoryRead = 110 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkMemory = 111 [(wire_debug_out) = true]; MessageType_DebugLinkMemory = 111 [(bitcoin_only) = true, (wire_debug_out) = true];
MessageType_DebugLinkMemoryWrite = 112 [(wire_debug_in) = true]; MessageType_DebugLinkMemoryWrite = 112 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkFlashErase = 113 [(wire_debug_in) = true]; MessageType_DebugLinkFlashErase = 113 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkLayout = 9001 [(bitcoin_only) = true, (wire_debug_out) = true];
MessageType_DebugLinkReseedRandom = 9002 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkRecordScreen = 9003 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkEraseSdCard = 9005 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkWatchLayout = 9006 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkResetDebugEvents = 9007 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkOptigaSetSecMax = 9008 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkGetGcInfo = 9009 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkGcInfo = 9010 [(bitcoin_only) = true, (wire_debug_out) = true];
MessageType_DebugLinkGetPairingInfo = 9011 [(bitcoin_only) = true, (wire_debug_in) = true];
MessageType_DebugLinkPairingInfo = 9012 [(bitcoin_only) = true, (wire_debug_out) = true];
// Ethereum // Ethereum
MessageType_EthereumGetPublicKey = 450 [(wire_in) = true]; MessageType_EthereumGetPublicKey = 450 [(wire_in) = true];
@ -116,11 +153,19 @@ enum MessageType {
MessageType_EthereumGetAddress = 56 [(wire_in) = true]; MessageType_EthereumGetAddress = 56 [(wire_in) = true];
MessageType_EthereumAddress = 57 [(wire_out) = true]; MessageType_EthereumAddress = 57 [(wire_out) = true];
MessageType_EthereumSignTx = 58 [(wire_in) = true]; MessageType_EthereumSignTx = 58 [(wire_in) = true];
MessageType_EthereumSignTxEIP1559 = 452 [(wire_in) = true];
MessageType_EthereumTxRequest = 59 [(wire_out) = true]; MessageType_EthereumTxRequest = 59 [(wire_out) = true];
MessageType_EthereumTxAck = 60 [(wire_in) = true]; MessageType_EthereumTxAck = 60 [(wire_in) = true];
MessageType_EthereumSignMessage = 64 [(wire_in) = true]; MessageType_EthereumSignMessage = 64 [(wire_in) = true];
MessageType_EthereumVerifyMessage = 65 [(wire_in) = true]; MessageType_EthereumVerifyMessage = 65 [(wire_in) = true];
MessageType_EthereumMessageSignature = 66 [(wire_out) = true]; MessageType_EthereumMessageSignature = 66 [(wire_out) = true];
MessageType_EthereumSignTypedData = 464 [(wire_in) = true];
MessageType_EthereumTypedDataStructRequest = 465 [(wire_out) = true];
MessageType_EthereumTypedDataStructAck = 466 [(wire_in) = true];
MessageType_EthereumTypedDataValueRequest = 467 [(wire_out) = true];
MessageType_EthereumTypedDataValueAck = 468 [(wire_in) = true];
MessageType_EthereumTypedDataSignature = 469 [(wire_out) = true];
MessageType_EthereumSignTypedHash = 470 [(wire_in) = true];
// NEM // NEM
MessageType_NEMGetAddress = 67 [(wire_in) = true]; MessageType_NEMGetAddress = 67 [(wire_in) = true];
@ -131,6 +176,7 @@ enum MessageType {
MessageType_NEMDecryptedMessage = 76 [(wire_out) = true]; MessageType_NEMDecryptedMessage = 76 [(wire_out) = true];
// Lisk // Lisk
/*
MessageType_LiskGetAddress = 114 [(wire_in) = true]; MessageType_LiskGetAddress = 114 [(wire_in) = true];
MessageType_LiskAddress = 115 [(wire_out) = true]; MessageType_LiskAddress = 115 [(wire_out) = true];
MessageType_LiskSignTx = 116 [(wire_in) = true]; MessageType_LiskSignTx = 116 [(wire_in) = true];
@ -140,6 +186,8 @@ enum MessageType {
MessageType_LiskVerifyMessage = 120 [(wire_in) = true]; MessageType_LiskVerifyMessage = 120 [(wire_in) = true];
MessageType_LiskGetPublicKey = 121 [(wire_in) = true]; MessageType_LiskGetPublicKey = 121 [(wire_in) = true];
MessageType_LiskPublicKey = 122 [(wire_out) = true]; MessageType_LiskPublicKey = 122 [(wire_out) = true];
*/
reserved 114 to 122;
// Tezos // Tezos
MessageType_TezosGetAddress = 150 [(wire_in) = true]; MessageType_TezosGetAddress = 150 [(wire_in) = true];
@ -156,48 +204,56 @@ enum MessageType {
MessageType_StellarAddress = 208 [(wire_out) = true]; MessageType_StellarAddress = 208 [(wire_out) = true];
MessageType_StellarCreateAccountOp = 210 [(wire_in) = true]; MessageType_StellarCreateAccountOp = 210 [(wire_in) = true];
MessageType_StellarPaymentOp = 211 [(wire_in) = true]; MessageType_StellarPaymentOp = 211 [(wire_in) = true];
MessageType_StellarPathPaymentOp = 212 [(wire_in) = true]; MessageType_StellarPathPaymentStrictReceiveOp = 212 [(wire_in) = true];
MessageType_StellarManageOfferOp = 213 [(wire_in) = true]; MessageType_StellarManageSellOfferOp = 213 [(wire_in) = true];
MessageType_StellarCreatePassiveOfferOp = 214 [(wire_in) = true]; MessageType_StellarCreatePassiveSellOfferOp = 214 [(wire_in) = true];
MessageType_StellarSetOptionsOp = 215 [(wire_in) = true]; MessageType_StellarSetOptionsOp = 215 [(wire_in) = true];
MessageType_StellarChangeTrustOp = 216 [(wire_in) = true]; MessageType_StellarChangeTrustOp = 216 [(wire_in) = true];
MessageType_StellarAllowTrustOp = 217 [(wire_in) = true]; MessageType_StellarAllowTrustOp = 217 [(wire_in) = true];
MessageType_StellarAccountMergeOp = 218 [(wire_in) = true]; MessageType_StellarAccountMergeOp = 218 [(wire_in) = true];
// omitted: StellarInflationOp is not a supported operation, would be 219 reserved 219; // omitted: StellarInflationOp
MessageType_StellarManageDataOp = 220 [(wire_in) = true]; MessageType_StellarManageDataOp = 220 [(wire_in) = true];
MessageType_StellarBumpSequenceOp = 221 [(wire_in) = true]; MessageType_StellarBumpSequenceOp = 221 [(wire_in) = true];
MessageType_StellarManageBuyOfferOp = 222 [(wire_in) = true];
MessageType_StellarPathPaymentStrictSendOp = 223 [(wire_in) = true];
reserved 224; // omitted: StellarCreateClaimableBalanceOp
MessageType_StellarClaimClaimableBalanceOp = 225 [(wire_in) = true];
MessageType_StellarSignedTx = 230 [(wire_out) = true]; MessageType_StellarSignedTx = 230 [(wire_out) = true];
// TRON
MessageType_TronGetAddress = 250 [(wire_in) = true];
MessageType_TronAddress = 251 [(wire_out) = true];
MessageType_TronSignTx = 252 [(wire_in) = true];
MessageType_TronSignedTx = 253 [(wire_out) = true];
// Cardano // Cardano
// dropped Sign/VerifyMessage ids 300-302 // dropped Sign/VerifyMessage ids 300-302
MessageType_CardanoSignTx = 303 [(wire_in) = true]; // dropped TxRequest/TxAck ids 304 and 309 (shelley update)
MessageType_CardanoTxRequest = 304 [(wire_out) = true]; // dropped SignTx/SignedTx/SignedTxChunk/SignedTxChunkAck ids 303, 310, 311 and 312
reserved 300 to 304, 309 to 312;
MessageType_CardanoGetPublicKey = 305 [(wire_in) = true]; MessageType_CardanoGetPublicKey = 305 [(wire_in) = true];
MessageType_CardanoPublicKey = 306 [(wire_out) = true]; MessageType_CardanoPublicKey = 306 [(wire_out) = true];
MessageType_CardanoGetAddress = 307 [(wire_in) = true]; MessageType_CardanoGetAddress = 307 [(wire_in) = true];
MessageType_CardanoAddress = 308 [(wire_out) = true]; MessageType_CardanoAddress = 308 [(wire_out) = true];
MessageType_CardanoTxAck = 309 [(wire_in) = true]; MessageType_CardanoTxItemAck = 313 [(wire_out) = true];
MessageType_CardanoSignedTx = 310 [(wire_out) = true]; MessageType_CardanoTxAuxiliaryDataSupplement = 314 [(wire_out) = true];
MessageType_CardanoTxWitnessRequest = 315 [(wire_in) = true];
// Ontology MessageType_CardanoTxWitnessResponse = 316 [(wire_out) = true];
MessageType_OntologyGetAddress = 350 [(wire_in) = true]; MessageType_CardanoTxHostAck = 317 [(wire_in) = true];
MessageType_OntologyAddress = 351 [(wire_out) = true]; MessageType_CardanoTxBodyHash = 318 [(wire_out) = true];
MessageType_OntologyGetPublicKey = 352 [(wire_in) = true]; MessageType_CardanoSignTxFinished = 319 [(wire_out) = true];
MessageType_OntologyPublicKey = 353 [(wire_out) = true]; MessageType_CardanoSignTxInit = 320 [(wire_in) = true];
MessageType_OntologySignTransfer = 354 [(wire_in) = true]; MessageType_CardanoTxInput = 321 [(wire_in) = true];
MessageType_OntologySignedTransfer = 355 [(wire_out) = true]; MessageType_CardanoTxOutput = 322 [(wire_in) = true];
MessageType_OntologySignWithdrawOng = 356 [(wire_in) = true]; MessageType_CardanoAssetGroup = 323 [(wire_in) = true];
MessageType_OntologySignedWithdrawOng = 357 [(wire_out) = true]; MessageType_CardanoToken = 324 [(wire_in) = true];
MessageType_OntologySignOntIdRegister = 358 [(wire_in) = true]; MessageType_CardanoTxCertificate = 325 [(wire_in) = true];
MessageType_OntologySignedOntIdRegister = 359 [(wire_out) = true]; MessageType_CardanoTxWithdrawal = 326 [(wire_in) = true];
MessageType_OntologySignOntIdAddAttributes = 360 [(wire_in) = true]; MessageType_CardanoTxAuxiliaryData = 327 [(wire_in) = true];
MessageType_OntologySignedOntIdAddAttributes = 361 [(wire_out) = true]; MessageType_CardanoPoolOwner = 328 [(wire_in) = true];
MessageType_CardanoPoolRelayParameters = 329 [(wire_in) = true];
MessageType_CardanoGetNativeScriptHash = 330 [(wire_in) = true];
MessageType_CardanoNativeScriptHash = 331 [(wire_out) = true];
MessageType_CardanoTxMint = 332 [(wire_in) = true];
MessageType_CardanoTxCollateralInput = 333 [(wire_in) = true];
MessageType_CardanoTxRequiredSigner = 334 [(wire_in) = true];
MessageType_CardanoTxInlineDatumChunk = 335 [(wire_in) = true];
MessageType_CardanoTxReferenceScriptChunk = 336 [(wire_in) = true];
MessageType_CardanoTxReferenceInput = 337 [(wire_in) = true];
// Ripple // Ripple
MessageType_RippleGetAddress = 400 [(wire_in) = true]; MessageType_RippleGetAddress = 400 [(wire_in) = true];
@ -210,8 +266,6 @@ enum MessageType {
MessageType_MoneroTransactionInitAck = 502 [(wire_out) = true]; MessageType_MoneroTransactionInitAck = 502 [(wire_out) = true];
MessageType_MoneroTransactionSetInputRequest = 503 [(wire_out) = true]; MessageType_MoneroTransactionSetInputRequest = 503 [(wire_out) = true];
MessageType_MoneroTransactionSetInputAck = 504 [(wire_out) = true]; MessageType_MoneroTransactionSetInputAck = 504 [(wire_out) = true];
MessageType_MoneroTransactionInputsPermutationRequest = 505 [(wire_out) = true];
MessageType_MoneroTransactionInputsPermutationAck = 506 [(wire_out) = true];
MessageType_MoneroTransactionInputViniRequest = 507 [(wire_out) = true]; MessageType_MoneroTransactionInputViniRequest = 507 [(wire_out) = true];
MessageType_MoneroTransactionInputViniAck = 508 [(wire_out) = true]; MessageType_MoneroTransactionInputViniAck = 508 [(wire_out) = true];
MessageType_MoneroTransactionAllInputsSetRequest = 509 [(wire_out) = true]; MessageType_MoneroTransactionAllInputsSetRequest = 509 [(wire_out) = true];
@ -253,15 +307,56 @@ enum MessageType {
MessageType_EosTxActionAck = 604 [(wire_in) = true]; MessageType_EosTxActionAck = 604 [(wire_in) = true];
MessageType_EosSignedTx = 605 [(wire_out) = true]; MessageType_EosSignedTx = 605 [(wire_out) = true];
// Binance // BNB Beacon Chain (deprecated)
MessageType_BinanceGetAddress = 700 [(wire_in) = true]; reserved 700 to 709;
MessageType_BinanceAddress = 701 [(wire_out) = true];
MessageType_BinanceGetPublicKey = 702 [(wire_in) = true]; // WebAuthn
MessageType_BinancePublicKey = 703 [(wire_out) = true]; MessageType_WebAuthnListResidentCredentials = 800 [(wire_in) = true];
MessageType_BinanceSignTx = 704 [(wire_in) = true]; MessageType_WebAuthnCredentials = 801 [(wire_out) = true];
MessageType_BinanceTxRequest = 705 [(wire_out) = true]; MessageType_WebAuthnAddResidentCredential = 802 [(wire_in) = true];
MessageType_BinanceTransferMsg = 706 [(wire_in) = true]; MessageType_WebAuthnRemoveResidentCredential = 803 [(wire_in) = true];
MessageType_BinanceOrderMsg = 707 [(wire_in) = true];
MessageType_BinanceCancelMsg = 708 [(wire_in) = true]; // Solana
MessageType_BinanceSignedTx = 709 [(wire_out) = true]; MessageType_SolanaGetPublicKey = 900 [(wire_in) = true];
MessageType_SolanaPublicKey = 901 [(wire_out) = true];
MessageType_SolanaGetAddress = 902 [(wire_in) = true];
MessageType_SolanaAddress = 903 [(wire_out) = true];
MessageType_SolanaSignTx = 904 [(wire_in) = true];
MessageType_SolanaTxSignature = 905 [(wire_out) = true];
// THP
MessageType_ThpCreateNewSession = 1000 [(bitcoin_only) = true, (wire_in) = true];
reserved 1001 to 1005; // never appeared in a release, reserved for future use
MessageType_ThpPairingRequest = 1006 [(bitcoin_only) = true];
MessageType_ThpPairingRequestApproved = 1007 [(bitcoin_only) = true];
MessageType_ThpSelectMethod = 1008 [(bitcoin_only) = true];
MessageType_ThpPairingPreparationsFinished = 1009 [(bitcoin_only) = true];
MessageType_ThpCredentialRequest = 1010 [(bitcoin_only) = true];
MessageType_ThpCredentialResponse = 1011 [(bitcoin_only) = true];
MessageType_ThpEndRequest = 1012 [(bitcoin_only) = true];
MessageType_ThpEndResponse = 1013 [(bitcoin_only) = true];
reserved 1014 to 1015;
MessageType_ThpCodeEntryCommitment = 1016 [(bitcoin_only) = true];
MessageType_ThpCodeEntryChallenge = 1017 [(bitcoin_only) = true];
MessageType_ThpCodeEntryCpaceTrezor = 1018 [(bitcoin_only) = true];
MessageType_ThpCodeEntryCpaceHostTag = 1019 [(bitcoin_only) = true];
MessageType_ThpCodeEntrySecret = 1020 [(bitcoin_only) = true];
reserved 1021 to 1023;
MessageType_ThpQrCodeTag = 1024 [(bitcoin_only) = true];
MessageType_ThpQrCodeSecret = 1025 [(bitcoin_only) = true];
reserved 1026 to 1031;
MessageType_ThpNfcTagHost = 1032 [(bitcoin_only) = true];
MessageType_ThpNfcTagTrezor = 1033 [(bitcoin_only) = true];
// Nostr
MessageType_NostrGetPubkey = 2001 [(wire_in) = true];
MessageType_NostrPubkey = 2002 [(wire_out) = true];
MessageType_NostrSignEvent = 2003 [(wire_in) = true];
MessageType_NostrEventSignature = 2004 [(wire_out) = true];
// Benchmark
MessageType_BenchmarkListNames = 9100 [(bitcoin_only) = true];
MessageType_BenchmarkNames = 9101 [(bitcoin_only) = true];
MessageType_BenchmarkRun = 9102 [(bitcoin_only) = true];
MessageType_BenchmarkResult = 9103 [(bitcoin_only) = true];
} }

View file

@ -0,0 +1,264 @@
// This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/options.proto
// dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.36.6
// protoc v4.25.3
// source: options.proto
package trezor
import (
reflect "reflect"
unsafe "unsafe"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
descriptorpb "google.golang.org/protobuf/types/descriptorpb"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
var file_options_proto_extTypes = []protoimpl.ExtensionInfo{
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50002,
Name: "hw.trezor.messages.wire_in",
Tag: "varint,50002,opt,name=wire_in",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50003,
Name: "hw.trezor.messages.wire_out",
Tag: "varint,50003,opt,name=wire_out",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50004,
Name: "hw.trezor.messages.wire_debug_in",
Tag: "varint,50004,opt,name=wire_debug_in",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50005,
Name: "hw.trezor.messages.wire_debug_out",
Tag: "varint,50005,opt,name=wire_debug_out",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50006,
Name: "hw.trezor.messages.wire_tiny",
Tag: "varint,50006,opt,name=wire_tiny",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50007,
Name: "hw.trezor.messages.wire_bootloader",
Tag: "varint,50007,opt,name=wire_bootloader",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 50008,
Name: "hw.trezor.messages.wire_no_fsm",
Tag: "varint,50008,opt,name=wire_no_fsm",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumValueOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 60000,
Name: "hw.trezor.messages.bitcoin_only",
Tag: "varint,60000,opt,name=bitcoin_only",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.EnumOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 51001,
Name: "hw.trezor.messages.has_bitcoin_only_values",
Tag: "varint,51001,opt,name=has_bitcoin_only_values",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.MessageOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 52001,
Name: "hw.trezor.messages.experimental_message",
Tag: "varint,52001,opt,name=experimental_message",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.MessageOptions)(nil),
ExtensionType: (*uint32)(nil),
Field: 52002,
Name: "hw.trezor.messages.wire_type",
Tag: "varint,52002,opt,name=wire_type",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.MessageOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 52003,
Name: "hw.trezor.messages.internal_only",
Tag: "varint,52003,opt,name=internal_only",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.FieldOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 53001,
Name: "hw.trezor.messages.experimental_field",
Tag: "varint,53001,opt,name=experimental_field",
Filename: "options.proto",
},
{
ExtendedType: (*descriptorpb.FileOptions)(nil),
ExtensionType: (*bool)(nil),
Field: 60000,
Name: "hw.trezor.messages.include_in_bitcoin_only",
Tag: "varint,60000,opt,name=include_in_bitcoin_only",
Filename: "options.proto",
},
}
// Extension fields to descriptorpb.EnumValueOptions.
var (
// optional bool wire_in = 50002;
E_WireIn = &file_options_proto_extTypes[0] // message can be transmitted via wire from PC to Trezor
// optional bool wire_out = 50003;
E_WireOut = &file_options_proto_extTypes[1] // message can be transmitted via wire from Trezor to PC
// optional bool wire_debug_in = 50004;
E_WireDebugIn = &file_options_proto_extTypes[2] // message can be transmitted via debug wire from PC to Trezor
// optional bool wire_debug_out = 50005;
E_WireDebugOut = &file_options_proto_extTypes[3] // message can be transmitted via debug wire from Trezor to PC
// optional bool wire_tiny = 50006;
E_WireTiny = &file_options_proto_extTypes[4] // message is handled by Trezor when the USB stack is in tiny mode
// optional bool wire_bootloader = 50007;
E_WireBootloader = &file_options_proto_extTypes[5] // message is only handled by Trezor Bootloader
// optional bool wire_no_fsm = 50008;
E_WireNoFsm = &file_options_proto_extTypes[6] // message is not handled by Trezor unless the USB stack is in tiny mode
// optional bool bitcoin_only = 60000;
E_BitcoinOnly = &file_options_proto_extTypes[7] // enum value is available on BITCOIN_ONLY build
)
// Extension fields to descriptorpb.EnumOptions.
var (
// optional bool has_bitcoin_only_values = 51001;
E_HasBitcoinOnlyValues = &file_options_proto_extTypes[8] // indicate that some values should be excluded on BITCOIN_ONLY builds
)
// Extension fields to descriptorpb.MessageOptions.
var (
// optional bool experimental_message = 52001;
E_ExperimentalMessage = &file_options_proto_extTypes[9] // indicate that a message is intended for development and beta testing only and its definition may change at any time
// optional uint32 wire_type = 52002;
E_WireType = &file_options_proto_extTypes[10] // override wire type specified in the MessageType enum
// optional bool internal_only = 52003;
E_InternalOnly = &file_options_proto_extTypes[11] // indicate that a message is intended for internal use only and should not be transmitted via the wire
)
// Extension fields to descriptorpb.FieldOptions.
var (
// optional bool experimental_field = 53001;
E_ExperimentalField = &file_options_proto_extTypes[12] // indicate that a field is intended for development and beta testing only
)
// Extension fields to descriptorpb.FileOptions.
var (
// optional bool include_in_bitcoin_only = 60000;
E_IncludeInBitcoinOnly = &file_options_proto_extTypes[13] // definitions are available on BITCOIN_ONLY build
)
var File_options_proto protoreflect.FileDescriptor
const file_options_proto_rawDesc = "" +
"\n" +
"\roptions.proto\x12\x12hw.trezor.messages\x1a google/protobuf/descriptor.proto:<\n" +
"\awire_in\x12!.google.protobuf.EnumValueOptions\x18҆\x03 \x01(\bR\x06wireIn:>\n" +
"\bwire_out\x12!.google.protobuf.EnumValueOptions\x18ӆ\x03 \x01(\bR\awireOut:G\n" +
"\rwire_debug_in\x12!.google.protobuf.EnumValueOptions\x18Ԇ\x03 \x01(\bR\vwireDebugIn:I\n" +
"\x0ewire_debug_out\x12!.google.protobuf.EnumValueOptions\x18Ն\x03 \x01(\bR\fwireDebugOut:@\n" +
"\twire_tiny\x12!.google.protobuf.EnumValueOptions\x18ֆ\x03 \x01(\bR\bwireTiny:L\n" +
"\x0fwire_bootloader\x12!.google.protobuf.EnumValueOptions\x18׆\x03 \x01(\bR\x0ewireBootloader:C\n" +
"\vwire_no_fsm\x12!.google.protobuf.EnumValueOptions\x18؆\x03 \x01(\bR\twireNoFsm:F\n" +
"\fbitcoin_only\x12!.google.protobuf.EnumValueOptions\x18\xe0\xd4\x03 \x01(\bR\vbitcoinOnly:U\n" +
"\x17has_bitcoin_only_values\x12\x1c.google.protobuf.EnumOptions\x18\xb9\x8e\x03 \x01(\bR\x14hasBitcoinOnlyValues:T\n" +
"\x14experimental_message\x12\x1f.google.protobuf.MessageOptions\x18\xa1\x96\x03 \x01(\bR\x13experimentalMessage:>\n" +
"\twire_type\x12\x1f.google.protobuf.MessageOptions\x18\xa2\x96\x03 \x01(\rR\bwireType:F\n" +
"\rinternal_only\x12\x1f.google.protobuf.MessageOptions\x18\xa3\x96\x03 \x01(\bR\finternalOnly:N\n" +
"\x12experimental_field\x12\x1d.google.protobuf.FieldOptions\x18\x89\x9e\x03 \x01(\bR\x11experimentalField:U\n" +
"\x17include_in_bitcoin_only\x12\x1c.google.protobuf.FileOptions\x18\xe0\xd4\x03 \x01(\bR\x14includeInBitcoinOnlyBo\n" +
"#com.satoshilabs.trezor.lib.protobufB\rTrezorOptionsZ9github.com/ethereum/go-ethereum/accounts/usbwallet/trezor"
var file_options_proto_goTypes = []any{
(*descriptorpb.EnumValueOptions)(nil), // 0: google.protobuf.EnumValueOptions
(*descriptorpb.EnumOptions)(nil), // 1: google.protobuf.EnumOptions
(*descriptorpb.MessageOptions)(nil), // 2: google.protobuf.MessageOptions
(*descriptorpb.FieldOptions)(nil), // 3: google.protobuf.FieldOptions
(*descriptorpb.FileOptions)(nil), // 4: google.protobuf.FileOptions
}
var file_options_proto_depIdxs = []int32{
0, // 0: hw.trezor.messages.wire_in:extendee -> google.protobuf.EnumValueOptions
0, // 1: hw.trezor.messages.wire_out:extendee -> google.protobuf.EnumValueOptions
0, // 2: hw.trezor.messages.wire_debug_in:extendee -> google.protobuf.EnumValueOptions
0, // 3: hw.trezor.messages.wire_debug_out:extendee -> google.protobuf.EnumValueOptions
0, // 4: hw.trezor.messages.wire_tiny:extendee -> google.protobuf.EnumValueOptions
0, // 5: hw.trezor.messages.wire_bootloader:extendee -> google.protobuf.EnumValueOptions
0, // 6: hw.trezor.messages.wire_no_fsm:extendee -> google.protobuf.EnumValueOptions
0, // 7: hw.trezor.messages.bitcoin_only:extendee -> google.protobuf.EnumValueOptions
1, // 8: hw.trezor.messages.has_bitcoin_only_values:extendee -> google.protobuf.EnumOptions
2, // 9: hw.trezor.messages.experimental_message:extendee -> google.protobuf.MessageOptions
2, // 10: hw.trezor.messages.wire_type:extendee -> google.protobuf.MessageOptions
2, // 11: hw.trezor.messages.internal_only:extendee -> google.protobuf.MessageOptions
3, // 12: hw.trezor.messages.experimental_field:extendee -> google.protobuf.FieldOptions
4, // 13: hw.trezor.messages.include_in_bitcoin_only:extendee -> google.protobuf.FileOptions
14, // [14:14] is the sub-list for method output_type
14, // [14:14] is the sub-list for method input_type
14, // [14:14] is the sub-list for extension type_name
0, // [0:14] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_options_proto_init() }
func file_options_proto_init() {
if File_options_proto != nil {
return
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: unsafe.Slice(unsafe.StringData(file_options_proto_rawDesc), len(file_options_proto_rawDesc)),
NumEnums: 0,
NumMessages: 0,
NumExtensions: 14,
NumServices: 0,
},
GoTypes: file_options_proto_goTypes,
DependencyIndexes: file_options_proto_depIdxs,
ExtensionInfos: file_options_proto_extTypes,
}.Build()
File_options_proto = out.File
file_options_proto_goTypes = nil
file_options_proto_depIdxs = nil
}

View file

@ -0,0 +1,72 @@
// This file originates from the SatoshiLabs Trezor `common` repository at:
// https://github.com/trezor/trezor-common/blob/master/protob/options.proto
// dated 30.06.2025, commit 421b45d4677f2499234692b0f54010bc45b3ae5f.
syntax = "proto2";
package hw.trezor.messages;
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 = "TrezorOptions";
import "google/protobuf/descriptor.proto";
/************************* WARNING ***********************
Due to the way extensions are accessed in pb2py, there needs to be a globally unique
name-ID mapping for extensions. That means that two different extensions, e.g. for
EnumValueOptions and FieldOptions, MUST NOT have the same ID.
Using the same ID indicates the same purpose (protobuf does not allow multiple
extensions with the same name), such as EnumValueOptions.bitcoin_only and
FileOptions.include_in_bitcoin_only. pb2py can then find the extension under
either name.
The convention to achieve this is as follows:
- extensions specific to a type have the same prefix:
* 50xxx for EnumValueOptions
* 51xxx for EnumOptions
* 52xxx for MessageOptions
* 53xxx for FieldOptions
- extensions that might be used across types have the same "global" prefix 60xxx
*/
/**
* Options for specifying message direction and type of wire (normal/debug)
*/
extend google.protobuf.EnumValueOptions {
optional bool wire_in = 50002; // message can be transmitted via wire from PC to Trezor
optional bool wire_out = 50003; // message can be transmitted via wire from Trezor to PC
optional bool wire_debug_in = 50004; // message can be transmitted via debug wire from PC to Trezor
optional bool wire_debug_out = 50005; // message can be transmitted via debug wire from Trezor to PC
optional bool wire_tiny = 50006; // message is handled by Trezor when the USB stack is in tiny mode
optional bool wire_bootloader = 50007; // message is only handled by Trezor Bootloader
optional bool wire_no_fsm = 50008; // message is not handled by Trezor unless the USB stack is in tiny mode
optional bool bitcoin_only = 60000; // enum value is available on BITCOIN_ONLY build
// (messages not marked bitcoin_only will be EXCLUDED)
}
/** Options for tagging enum types */
extend google.protobuf.EnumOptions {
optional bool has_bitcoin_only_values = 51001; // indicate that some values should be excluded on BITCOIN_ONLY builds
}
/** Options for tagging message types */
extend google.protobuf.MessageOptions {
optional bool experimental_message = 52001; // indicate that a message is intended for development and beta testing only and its definition may change at any time
optional uint32 wire_type = 52002; // override wire type specified in the MessageType enum
optional bool internal_only = 52003; // indicate that a message is intended for internal use only and should not be transmitted via the wire
}
/** Options for tagging field types */
extend google.protobuf.FieldOptions {
optional bool experimental_field = 53001; // indicate that a field is intended for development and beta testing only
}
/** Options for tagging files with protobuf definitions */
extend google.protobuf.FileOptions {
optional bool include_in_bitcoin_only = 60000; // definitions are available on BITCOIN_ONLY build
// intentionally identical to `bitcoin_only` from enum
}

View file

@ -42,7 +42,7 @@
// - Grab the latest Go plugin `go get -u google.golang.org/protobuf/cmd/protoc-gen-go` // - Grab the latest Go plugin `go get -u google.golang.org/protobuf/cmd/protoc-gen-go`
// - Vendor in the latest Go plugin `govendor fetch google.golang.org/protobuf/...` // - Vendor in the latest Go plugin `govendor fetch google.golang.org/protobuf/...`
//go:generate protoc -I/usr/local/include:. --go_out=paths=source_relative:. messages.proto messages-common.proto messages-management.proto messages-ethereum.proto //go:generate protoc -I/usr/local/include:. --go_out=paths=source_relative:. options.proto messages.proto messages-common.proto messages-management.proto messages-ethereum.proto messages-ethereum-eip712.proto
// Package trezor contains the wire protocol. // Package trezor contains the wire protocol.
package trezor package trezor

View file

@ -19,6 +19,7 @@ package usbwallet
import ( import (
"context" "context"
"encoding/json"
"fmt" "fmt"
"io" "io"
"math/big" "math/big"
@ -31,7 +32,8 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/karalabe/hid" "github.com/ethereum/go-ethereum/signer/core/apitypes"
"github.com/karalabe/usb"
) )
// Maximum time between wallet health checks to detect USB unplugs. // Maximum time between wallet health checks to detect USB unplugs.
@ -68,7 +70,17 @@ type driver interface {
// or deny the transaction. // or deny the transaction.
SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error)
SignTypedMessage(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error) // SignText sends a message to sign to the USB device and waits for the user to confirm
// or deny the signature.
SignText(path accounts.DerivationPath, text []byte) ([]byte, error)
// SignTypedHash sends a typed message to sign to the USB device and waits for the user to confirm
// or deny the signature.
SignTypedHash(path accounts.DerivationPath, messageHash []byte, domainHash []byte) ([]byte, error)
// SignedTypedData sends a typed data struct to sign to the USB device and waits for the user to confirm
// or deny the signature.
SignedTypedData(path accounts.DerivationPath, data apitypes.TypedData) ([]byte, error)
} }
// wallet represents the common functionality shared by all USB hardware // wallet represents the common functionality shared by all USB hardware
@ -79,8 +91,8 @@ type wallet struct {
driver driver // Hardware implementation of the low level device operations driver driver // Hardware implementation of the low level device operations
url *accounts.URL // Textual URL uniquely identifying this wallet url *accounts.URL // Textual URL uniquely identifying this wallet
info hid.DeviceInfo // Known USB device infos about the wallet info usb.DeviceInfo // Known USB device infos about the wallet
device hid.Device // USB device advertising itself as a hardware wallet device usb.Device // USB device advertising itself as a hardware wallet
accounts []accounts.Account // List of derive accounts pinned on the hardware wallet accounts []accounts.Account // List of derive accounts pinned on the hardware wallet
paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations paths map[common.Address]accounts.DerivationPath // Known derivation paths for signing operations
@ -530,45 +542,29 @@ func (w *wallet) signHash(account accounts.Account, hash []byte) ([]byte, error)
// SignData signs keccak256(data). The mimetype parameter describes the type of data being signed // SignData signs keccak256(data). The mimetype parameter describes the type of data being signed
func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) { func (w *wallet) SignData(account accounts.Account, mimeType string, data []byte) ([]byte, error) {
// Unless we are doing 712 signing, simply dispatch to signHash path, done, err := w.lockAndDerivePath(account)
if !(mimeType == accounts.MimetypeTypedData && len(data) == 66 && data[0] == 0x19 && data[1] == 0x01) {
return w.signHash(account, crypto.Keccak256(data))
}
// dispatch to 712 signing if the mimetype is TypedData and the format matches
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
defer w.stateLock.RUnlock()
// If the wallet is closed, abort
if w.device == nil {
return nil, accounts.ErrWalletClosed
}
// Make sure the requested account is contained within
path, ok := w.paths[account.Address]
if !ok {
return nil, accounts.ErrUnknownAccount
}
// All infos gathered and metadata checks out, request signing
<-w.commsLock
defer func() { w.commsLock <- struct{}{} }()
// Ensure the device isn't screwed with while user confirmation is pending
// TODO(karalabe): remove if hotplug lands on Windows
w.hub.commsLock.Lock()
w.hub.commsPend++
w.hub.commsLock.Unlock()
defer func() {
w.hub.commsLock.Lock()
w.hub.commsPend--
w.hub.commsLock.Unlock()
}()
// Sign the transaction
signature, err := w.driver.SignTypedMessage(path, data[2:34], data[34:66])
if err != nil { if err != nil {
return nil, err return nil, err
} }
return signature, nil defer done()
if mimeType == accounts.MimetypeTypedData {
// If the data is exactly 66 bytes and starts with the 0x19 0x01 prefix, treat
// it as the raw EIP-712 hash pair (domain and message) to sign.
if len(data) == 66 && data[0] == 0x19 && data[1] == 0x01 {
return w.driver.SignTypedHash(path, data[2:34], data[34:66])
}
// Otherwise attempt to parse the data as a JSON encoded EIP-712 struct
// and sign it as such.
var typedData apitypes.TypedData
if err := json.Unmarshal(data, &typedData); err != nil {
return nil, fmt.Errorf("invalid typed data: %w", err)
}
return w.driver.SignedTypedData(path, typedData)
}
// If we're not doing 712 signing, simply dispatch to signHash
return w.signHash(account, crypto.Keccak256(data))
} }
// SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given // SignDataWithPassphrase implements accounts.Wallet, attempting to sign the given
@ -578,8 +574,21 @@ func (w *wallet) SignDataWithPassphrase(account accounts.Account, passphrase, mi
return w.SignData(account, mimeType, data) return w.SignData(account, mimeType, data)
} }
// SignText implements accounts.Wallet, signing the text as an EIP-712 personal
// message.
func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) { func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error) {
return w.signHash(account, accounts.TextHash(text)) path, done, err := w.lockAndDerivePath(account)
if err != nil {
return nil, err
}
defer done()
// Sign the transaction
signature, err := w.driver.SignText(path, text)
if err != nil {
return nil, err
}
return signature, nil
} }
// SignTx implements accounts.Wallet. It sends the transaction over to the Ledger // SignTx implements accounts.Wallet. It sends the transaction over to the Ledger
@ -590,33 +599,12 @@ func (w *wallet) SignText(account accounts.Account, text []byte) ([]byte, error)
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error // too old to sign EIP-155 transactions, but such is requested nonetheless, an error
// will be returned opposed to silently signing in Homestead mode. // will be returned opposed to silently signing in Homestead mode.
func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
w.stateLock.RLock() // Comms have own mutex, this is for the state fields path, done, err := w.lockAndDerivePath(account)
defer w.stateLock.RUnlock() if err != nil {
return nil, err
// If the wallet is closed, abort
if w.device == nil {
return nil, accounts.ErrWalletClosed
} }
// Make sure the requested account is contained within defer done()
path, ok := w.paths[account.Address]
if !ok {
return nil, accounts.ErrUnknownAccount
}
// All infos gathered and metadata checks out, request signing
<-w.commsLock
defer func() { w.commsLock <- struct{}{} }()
// Ensure the device isn't screwed with while user confirmation is pending
// TODO(karalabe): remove if hotplug lands on Windows
w.hub.commsLock.Lock()
w.hub.commsPend++
w.hub.commsLock.Unlock()
defer func() {
w.hub.commsLock.Lock()
w.hub.commsPend--
w.hub.commsLock.Unlock()
}()
// Sign the transaction and verify the sender to avoid hardware fault surprises // Sign the transaction and verify the sender to avoid hardware fault surprises
sender, signed, err := w.driver.SignTx(path, tx, chainID) sender, signed, err := w.driver.SignTx(path, tx, chainID)
if err != nil { if err != nil {
@ -628,11 +616,10 @@ func (w *wallet) SignTx(account accounts.Account, tx *types.Transaction, chainID
return signed, nil return signed, nil
} }
// SignTextWithPassphrase implements accounts.Wallet, however signing arbitrary // SignTextWithPassphrase implements accounts.Wallet, signing the text as an EIP-712
// data is not supported for Ledger wallets, so this method will always return // personal message.
// an error.
func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) { func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase string, text []byte) ([]byte, error) {
return w.SignText(account, accounts.TextHash(text)) return w.SignText(account, text)
} }
// SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given // SignTxWithPassphrase implements accounts.Wallet, attempting to sign the given
@ -641,3 +628,38 @@ func (w *wallet) SignTextWithPassphrase(account accounts.Account, passphrase str
func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) { func (w *wallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
return w.SignTx(account, tx, chainID) return w.SignTx(account, tx, chainID)
} }
func (w *wallet) lockAndDerivePath(account accounts.Account) (accounts.DerivationPath, func(), error) {
w.stateLock.RLock() // Comms have own mutex, this is for the state fields
// If the wallet is closed, abort
if w.device == nil {
w.stateLock.RUnlock()
return nil, nil, accounts.ErrWalletClosed
}
// Make sure the requested account is contained within
path, ok := w.paths[account.Address]
if !ok {
w.stateLock.RUnlock()
return nil, nil, accounts.ErrUnknownAccount
}
// All infos gathered and metadata checks out, request signing
<-w.commsLock
// Ensure the device isn't screwed with while user confirmation is pending
// TODO(karalabe): remove if hotplug lands on Windows
w.hub.commsLock.Lock()
w.hub.commsPend++
w.hub.commsLock.Unlock()
done := func() {
w.stateLock.RUnlock()
w.commsLock <- struct{}{}
w.hub.commsLock.Lock()
w.hub.commsPend--
w.hub.commsLock.Unlock()
}
return path, done, nil
}

4
go.mod
View file

@ -45,7 +45,7 @@ require (
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
github.com/jackpal/go-nat-pmp v1.0.2 github.com/jackpal/go-nat-pmp v1.0.2
github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267 github.com/jedisct1/go-minisign v0.0.0-20230811132847-661be99b8267
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 github.com/karalabe/usb v0.0.3-0.20231219215548-8627268f6b0a
github.com/kylelemons/godebug v1.1.0 github.com/kylelemons/godebug v1.1.0
github.com/mattn/go-colorable v0.1.13 github.com/mattn/go-colorable v0.1.13
github.com/mattn/go-isatty v0.0.20 github.com/mattn/go-isatty v0.0.20
@ -56,6 +56,7 @@ require (
github.com/protolambda/bls12-381-util v0.1.0 github.com/protolambda/bls12-381-util v0.1.0
github.com/protolambda/zrnt v0.34.1 github.com/protolambda/zrnt v0.34.1
github.com/protolambda/ztyp v0.2.2 github.com/protolambda/ztyp v0.2.2
github.com/reserve-protocol/trezor v0.0.0-20190523030725-9e38328dde28
github.com/rs/cors v1.7.0 github.com/rs/cors v1.7.0
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible
github.com/status-im/keycard-go v0.2.0 github.com/status-im/keycard-go v0.2.0
@ -126,6 +127,7 @@ require (
github.com/mitchellh/mapstructure v1.4.1 // indirect github.com/mitchellh/mapstructure v1.4.1 // indirect
github.com/mitchellh/pointerstructure v1.2.0 // indirect github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/naoina/go-stringutil v0.1.0 // indirect github.com/naoina/go-stringutil v0.1.0 // indirect
github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e // indirect
github.com/opentracing/opentracing-go v1.1.0 // indirect github.com/opentracing/opentracing-go v1.1.0 // indirect
github.com/pion/dtls/v2 v2.2.7 // indirect github.com/pion/dtls/v2 v2.2.7 // indirect
github.com/pion/logging v0.2.2 // indirect github.com/pion/logging v0.2.2 // indirect

9
go.sum
View file

@ -215,8 +215,8 @@ github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9Y
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52 h1:msKODTL1m0wigztaqILOtla9HeW1ciscYG4xjLtvk5I= github.com/karalabe/usb v0.0.3-0.20231219215548-8627268f6b0a h1:BGJeMa7efLsbPri2WJxtFOcjDPyjCdNlZWERE11GJAE=
github.com/karalabe/hid v1.0.1-0.20240306101548-573246063e52/go.mod h1:qk1sX/IBgppQNcGCRoj90u6EGC056EBoIc1oEjCWla8= github.com/karalabe/usb v0.0.3-0.20231219215548-8627268f6b0a/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4= github.com/kilic/bls12-381 v0.1.0 h1:encrdjqKMEvabVQ7qYOKu1OvhqpK4s47wDYtNiPtlp4=
github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig= github.com/kilic/bls12-381 v0.1.0/go.mod h1:vDTTHJONJ6G+P2R74EhnyotQDTliQDnFEwhdmfzw1ig=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
@ -256,6 +256,7 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@ -271,6 +272,8 @@ github.com/naoina/go-stringutil v0.1.0 h1:rCUeRUHjBjGTSHl0VC00jUPLz8/F9dDzYI70Hz
github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0= github.com/naoina/go-stringutil v0.1.0/go.mod h1:XJ2SJL9jCtBh+P9q5btrd/Ylo8XwT/h1USek5+NqSA0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416 h1:shk/vn9oCoOTmwcouEdwIeOtOGA/ELRUw/GwvxwfT+0=
github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E= github.com/naoina/toml v0.1.2-0.20170918210437-9fafd6967416/go.mod h1:NBIhNtsFMo3G2szEBne+bO4gS192HuIYRqfvOWb4i1E=
github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e h1:Vbib8wJAaMEF9jusI/kMSYMr/LtRzM7+F9MJgt/nH8k=
github.com/nsf/termbox-go v0.0.0-20190325093121-288510b9734e/go.mod h1:IuKpRQcYE1Tfu+oAQqaLisqDeXgjyyltCfsaoYN18NQ=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78= github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
@ -324,6 +327,8 @@ github.com/protolambda/ztyp v0.2.2 h1:rVcL3vBu9W/aV646zF6caLS/dyn9BN8NYiuJzicLNy
github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU= github.com/protolambda/ztyp v0.2.2/go.mod h1:9bYgKGqg3wJqT9ac1gI2hnVb0STQq7p/1lapqrqY1dU=
github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4= github.com/prysmaticlabs/gohashtree v0.0.4-beta h1:H/EbCuXPeTV3lpKeXGPpEV9gsUpkqOOVnWapUyeWro4=
github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs= github.com/prysmaticlabs/gohashtree v0.0.4-beta/go.mod h1:BFdtALS+Ffhg3lGQIHv9HDWuHS8cTvHZzrHWxwOtGOs=
github.com/reserve-protocol/trezor v0.0.0-20190523030725-9e38328dde28 h1:0XRrYV4bb+Y09cLQKsVE5gQ3HRebPXxY89zx+ZV2Fo0=
github.com/reserve-protocol/trezor v0.0.0-20190523030725-9e38328dde28/go.mod h1:Twg3zlQzleALAU+gR5xSK112gdh9YLcQ6owamVh0lbY=
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc=