Skip to content

Commit

Permalink
revert using karalabe hid because trezor needs libusb, pull latest so…
Browse files Browse the repository at this point in the history
…urcecode of hidapi to fix ledger connection issue
  • Loading branch information
tranvictor committed Apr 24, 2024
1 parent 76f59bf commit 5df47ee
Show file tree
Hide file tree
Showing 194 changed files with 37,061 additions and 1,622 deletions.
2 changes: 1 addition & 1 deletion common/printer.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func PrintElapseTime(start time.Time, str string) {

func DebugPrintf(format string, a ...any) (n int, err error) {
if config.Debug {
return fmt.Printf(format, a)
return fmt.Printf(format, a...)
}

return 0, nil
Expand Down
169 changes: 99 additions & 70 deletions util/account/ledgereum/ledger_driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,12 @@ func (w *ledgerDriver) Status() (string, error) {
if w.offline() {
return "Ethereum app offline", w.failure
}
return fmt.Sprintf("Ethereum app v%d.%d.%d online", w.version[0], w.version[1], w.version[2]), w.failure
return fmt.Sprintf(
"Ethereum app v%d.%d.%d online",
w.version[0],
w.version[1],
w.version[2],
), w.failure
}

// offline returns whether the wallet and the Ethereum app is offline or not.
Expand Down Expand Up @@ -151,14 +156,23 @@ func (w *ledgerDriver) Derive(path accounts.DerivationPath) (common.Address, err
// Note, if the version of the Ethereum application running on the Ledger wallet is
// too old to sign EIP-155 transactions, but such is requested nonetheless, an error
// will be returned opposed to silently signing in Homestead mode.
func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transaction, chainId *big.Int) (common.Address, *types.Transaction, error) {
func (w *ledgerDriver) SignTx(
path accounts.DerivationPath,
tx *types.Transaction,
chainId *big.Int,
) (common.Address, *types.Transaction, error) {
// If the Ethereum app doesn't run, abort
if w.offline() {
return common.Address{}, nil, accounts.ErrWalletClosed
}
// Ensure the wallet is capable of signing the given transaction
if chainId != nil && w.version[0] <= 1 && w.version[1] <= 0 && w.version[2] <= 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])
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],
)
}
// All infos gathered and metadata checks out, request signing
return w.ledgerSign(path, tx, chainId)
Expand All @@ -169,18 +183,18 @@ func (w *ledgerDriver) SignTx(path accounts.DerivationPath, tx *types.Transactio
//
// The version retrieval protocol is defined as follows:
//
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+----+---
// E0 | 06 | 00 | 00 | 00 | 04
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+----+---
// E0 | 06 | 00 | 00 | 00 | 04
//
// With no input data, and the output data being:
//
// Description | Length
// ---------------------------------------------------+--------
// Flags 01: arbitrary data signature enabled by user | 1 byte
// Application major version | 1 byte
// Application minor version | 1 byte
// Application patch version | 1 byte
// Description | Length
// ---------------------------------------------------+--------
// Flags 01: arbitrary data signature enabled by user | 1 byte
// Application major version | 1 byte
// Application minor version | 1 byte
// Application patch version | 1 byte
func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
// Send the request and wait for the response
reply, err := w.ledgerExchange(ledgerOpGetConfiguration, 0, 0, nil)
Expand All @@ -201,32 +215,32 @@ func (w *ledgerDriver) ledgerVersion() ([3]byte, error) {
//
// The address derivation protocol is defined as follows:
//
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+-----+---
// E0 | 02 | 00 return address
// 01 display address and confirm before returning
// | 00: do not return the chain code
// | 01: return the chain code
// | var | 00
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+-----+---
// E0 | 02 | 00 return address
// 01 display address and confirm before returning
// | 00: do not return the chain code
// | 01: return the chain code
// | var | 00
//
// Where the input data 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
// 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
// ------------------------+-------------------
// Public Key length | 1 byte
// Uncompressed Public Key | arbitrary
// Ethereum address length | 1 byte
// Ethereum address | 40 bytes hex ascii
// Chain code if requested | 32 bytes
// Description | Length
// ------------------------+-------------------
// Public Key length | 1 byte
// Uncompressed Public Key | arbitrary
// Ethereum address length | 1 byte
// Ethereum address | 40 bytes hex ascii
// Chain code if requested | 32 bytes
func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, error) {
// Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath))
Expand All @@ -235,7 +249,12 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
binary.BigEndian.PutUint32(path[1+4*i:], component)
}
// Send the request and wait for the response
reply, err := w.ledgerExchange(ledgerOpRetrieveAddress, ledgerP1DirectlyFetchAddress, ledgerP2DiscardAddressChainCode, path)
reply, err := w.ledgerExchange(
ledgerOpRetrieveAddress,
ledgerP1DirectlyFetchAddress,
ledgerP2DiscardAddressChainCode,
path,
)
if err != nil {
return common.Address{}, err
}
Expand Down Expand Up @@ -264,36 +283,40 @@ func (w *ledgerDriver) ledgerDerive(derivationPath []uint32) (common.Address, er
//
// The transaction signing protocol is defined as follows:
//
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+-----+---
// E0 | 04 | 00: first transaction data block
// 80: subsequent transaction data block
// | 00 | variable | variable
// CLA | INS | P1 | P2 | Lc | Le
// ----+-----+----+----+-----+---
// E0 | 04 | 00: first transaction data block
// 80: subsequent transaction data block
// | 00 | variable | variable
//
// Where the input for the first transaction block (first 255 bytes) 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
// RLP transaction chunk | arbitrary
// 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
// RLP transaction chunk | arbitrary
//
// And the input for subsequent transaction blocks (first 255 bytes) are:
//
// Description | Length
// ----------------------+----------
// RLP transaction chunk | arbitrary
// Description | Length
// ----------------------+----------
// RLP transaction chunk | arbitrary
//
// And the output data is:
//
// Description | Length
// ------------+---------
// signature V | 1 byte
// signature R | 32 bytes
// signature S | 32 bytes
func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction, chainId *big.Int) (common.Address, *types.Transaction, error) {
// Description | Length
// ------------+---------
// signature V | 1 byte
// signature R | 32 bytes
// signature S | 32 bytes
func (w *ledgerDriver) ledgerSign(
derivationPath []uint32,
tx *types.Transaction,
chainId *big.Int,
) (common.Address, *types.Transaction, error) {
// Flatten the derivation path into the Ledger request
path := make([]byte, 1+4*len(derivationPath))
path[0] = byte(len(derivationPath))
Expand Down Expand Up @@ -366,12 +389,12 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
//
// The common transport header is defined as follows:
//
// Description | Length
// --------------------------------------+----------
// Communication channel ID (big endian) | 2 bytes
// Command tag | 1 byte
// Packet sequence index (big endian) | 2 bytes
// Payload | arbitrary
// Description | Length
// --------------------------------------+----------
// Communication channel ID (big endian) | 2 bytes
// Command tag | 1 byte
// Packet sequence index (big endian) | 2 bytes
// Payload | arbitrary
//
// The Communication channel ID allows commands multiplexing over the same
// physical link. It is not used for the time being, and should be set to 0101
Expand All @@ -385,16 +408,21 @@ func (w *ledgerDriver) ledgerSign(derivationPath []uint32, tx *types.Transaction
//
// APDU Command payloads are encoded as follows:
//
// Description | Length
// -----------------------------------
// APDU length (big endian) | 2 bytes
// APDU CLA | 1 byte
// APDU INS | 1 byte
// APDU P1 | 1 byte
// APDU P2 | 1 byte
// APDU length | 1 byte
// Optional APDU data | arbitrary
func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 ledgerParam2, data []byte) ([]byte, error) {
// Description | Length
// -----------------------------------
// APDU length (big endian) | 2 bytes
// APDU CLA | 1 byte
// APDU INS | 1 byte
// APDU P1 | 1 byte
// APDU P2 | 1 byte
// APDU length | 1 byte
// Optional APDU data | arbitrary
func (w *ledgerDriver) ledgerExchange(
opcode ledgerOpcode,
p1 ledgerParam1,
p2 ledgerParam2,
data []byte,
) ([]byte, error) {
// Construct the message payload, possibly split into multiple chunks
apdu := make([]byte, 2, 7+len(data))

Expand All @@ -421,6 +449,7 @@ func (w *ledgerDriver) ledgerExchange(opcode ledgerOpcode, p1 ledgerParam1, p2 l
}
// Send over to the device
fmt.Printf("Data chunk sent to the Ledger: %s\n", hexutil.Bytes(chunk))
fmt.Printf("driver instance: %w\n", w.device)

Check failure on line 452 in util/account/ledgereum/ledger_driver.go

View workflow job for this annotation

GitHub Actions / build

fmt.Printf format %w has arg w.device of wrong type io.ReadWriter
if _, err := w.device.Write(chunk); err != nil {
return nil, err
}
Expand Down
30 changes: 26 additions & 4 deletions util/account/ledgereum/ledgereum.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"

. "github.com/tranvictor/jarvis/common"
kusb "github.com/tranvictor/jarvis/util/account/usb"
)

Expand All @@ -18,18 +20,27 @@ const (
)

var LEDGER_PRODUCT_IDS []uint16 = []uint16{
// Device definitions taken from
// https://github.com/LedgerHQ/ledger-live/blob/38012bc8899e0f07149ea9cfe7e64b2c146bc92b/libs/ledgerjs/packages/devices/src/index.ts

// Original product IDs
0x0000, /* Ledger Blue */
0x0001, /* Ledger Nano S */
0x0004, /* Ledger Nano X */
0x0005, /* Ledger Nano S Plus */
0x0006, /* Ledger Nano FTS */

// Upcoming product IDs: https://www.ledger.com/2019/05/17/windows-10-update-sunsetting-u2f-tunnel-transport-for-ledger-devices/
0x0015, /* HID + U2F + WebUSB Ledger Blue */
0x1015, /* HID + U2F + WebUSB Ledger Nano S */
0x4015, /* HID + U2F + WebUSB Ledger Nano X */
0x5015, /* HID + U2F + WebUSB Ledger Nano S Plus */
0x6015, /* HID + U2F + WebUSB Ledger Nano FTS */

0x0011, /* HID + WebUSB Ledger Blue */
0x1011, /* HID + WebUSB Ledger Nano S */
0x4011, /* HID + WebUSB Ledger Nano X */
0x5011, /* HID + WebUSB Ledger Nano S Plus */
0x6011, /* HID + WebUSB Ledger Nano FTS */
}

type Ledgereum struct {
Expand Down Expand Up @@ -59,12 +70,14 @@ func (self *Ledgereum) Unlock() error {
if len(infos) == 0 {
return fmt.Errorf("Ledger device is not found")
} else {
for _, info := range infos {
// fmt.Printf("Device %d: Vendor ID: %d, %v\n", i, info.VendorID, info)
for i, info := range infos {
DebugPrintf("Device %d: Vendor ID: %d, %v\n", i, info.VendorID, info)
for _, id := range LEDGER_PRODUCT_IDS {
// Windows and Macos use UsageID matching, Linux uses Interface matching
if info.ProductID == id && (info.UsagePage == LEDGER_USAGE_ID || info.Interface == LEDGER_ENDPOINT_ID) {
DebugPrintf("setting up device instance...")
self.device, err = info.Open()
DebugPrintf("done. Instance: %v, err: %v\n", self.device, err)
if err != nil {
return err
}
Expand All @@ -76,6 +89,11 @@ func (self *Ledgereum) Unlock() error {
}
}
}

if self.device == nil {
return fmt.Errorf("Ledger device is not found")
}

self.deviceUnlocked = true
return nil
}
Expand All @@ -84,6 +102,10 @@ func (self *Ledgereum) Derive(path accounts.DerivationPath) (common.Address, err
return self.driver.Derive(path)
}

func (self *Ledgereum) Sign(path accounts.DerivationPath, tx *types.Transaction, chainID *big.Int) (common.Address, *types.Transaction, error) {
func (self *Ledgereum) Sign(
path accounts.DerivationPath,
tx *types.Transaction,
chainID *big.Int,
) (common.Address, *types.Transaction, error) {
return self.driver.SignTx(path, tx, chainID)
}
2 changes: 1 addition & 1 deletion util/account/usb/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ There are multiple already existing USB libraries:
* Unfortunately, `gousb` requires the `libusb` C library to be installed both during build as well as during runtime on the host operating system. This breaks binary portability and also adds unnecessary hurdles on Windows.
* Furthermore, whilst HID devices are supported by `libusb`, the OS on Macos and Windows explicitly takes over these devices, so only native system calls can be used on recent versions (i.e. you **cannot** use `libusb` for HID).
* There is a fork of `gousb` [created by @karalabe](https://github.com/karalabe/gousb) that statically linked `libusb` during build, but with the lack of HID access, that work was abandoned.
* For HID-only devices, a previous self-contained package was created at [`github.com/karalabe/hid`](https://github.com/karalabe/hid), which worked well for hardware wallet uses cases in [`go-ethereum`](https://github.com/ethereum/go-ethereum). It's a simple package that does it's thing well.
* For HID-only devices, a previous self-contained package was created at [`github.com/karalabe/hid`](https://github.com/karalabe/hid), which worked well for hardware wallet uses cases in [`go-ethereum`](https://github.com/ethereum/go-ethereum). It's a simple package that does its thing well.
* Unfortunately, `hid` is not capable of talking to generic USB devices. When multiple different devices are needed, eventually some will not support the HID spec (e.g. WebUSB). Pulling in both `hid` and `gousb` will break down due to both depending internally on different versions of `libusb` on Linux.

This `usb` package is a proper integration of `hidapi` and `libusb` so that communication with HID devices is done via system calls, whereas communication with lower level USB devices is done via interrupts. All this detail is hidden away behind a tiny interface.
Expand Down
7 changes: 4 additions & 3 deletions util/account/usb/hid_enabled.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// You should have received a copy of the GNU Lesser General Public License along
// with the library. If not, see <http://www.gnu.org/licenses/>.

//go:build (freebsd && cgo) || (linux && cgo) || (darwin && !ios && cgo) || (windows && cgo)
// +build freebsd,cgo linux,cgo darwin,!ios,cgo windows,cgo

package usb
Expand All @@ -32,9 +33,9 @@ import (

// enumerateHid returns a list of all the HID devices attached to the system which
// match the vendor and product id:
// - If the vendor id is set to 0 then any vendor matches.
// - If the product id is set to 0 then any product matches.
// - If the vendor and product id are both 0, all HID devices are returned.
// - If the vendor id is set to 0 then any vendor matches.
// - If the product id is set to 0 then any product matches.
// - If the vendor and product id are both 0, all HID devices are returned.
func enumerateHid(vendorID uint16, productID uint16) ([]DeviceInfo, error) {
// Gather all device infos and ensure they are freed before returning
head := C.hid_enumerate(C.ushort(vendorID), C.ushort(productID))
Expand Down
Loading

0 comments on commit 5df47ee

Please sign in to comment.