ledger

package module
v1.1.7 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 16, 2026 License: Apache-2.0 Imports: 15 Imported by: 3

README

Ledger Lux

License GithubActions

Overview

This repository contains the Go client library for interacting with Lux apps on Ledger hardware wallets.

This is a Go-first repository with the following structure:

  • Top level: Go client library (github.com/luxfi/ledger)
  • /rust/: Rust implementation and Ledger app source
  • /go-docs/: Additional Go documentation

Installation

go get github.com/luxfi/ledger

Usage

import "github.com/luxfi/ledger"

// Example usage
app, err := ledger.FindLedgerLuxUserApp()
if err != nil {
    log.Fatal(err)
}
defer app.Close()

version, err := app.GetVersion()
if err != nil {
    log.Fatal(err)
}

fmt.Printf("App Version: %s\n", version)

Features

  • BIP32/BIP44 key derivation
  • P2PKH address generation
  • Transaction signing
  • Message signing
  • Multi-signature support
  • Multi-chain: one Ledger app signs for Lux, Hanzo, Zoo, and Pars

Chains

Chain SLIP-0044 EIP-155 chain_id CLA Notes
Lux 9000 96369 0x80 Native P/X chains + EVM C-Chain
Hanzo 60 36963 0xE0 EVM-compat
Zoo 60 200200 0xE0 EVM-compat
Pars 60 7777 0xE0 EVM-compat

EVM-compat chains share derivation path m/44'/60'/... so a single hardware key roams across the family; EIP-155 chain_id provides replay protection. See docs/CHAIN_PATHS.md for the full path map.

path, err := ledger.BIP44PathForName("hanzo", 0, 0, 0)
// path == "m/44'/60'/0'/0/0"

Testing

Tests require a physical Ledger device with the Lux app installed:

go test ./...

Development

Ledger App Development

The Ledger app source code is located in the /rust/ directory. See /rust/README.md for build instructions.

Go Library Development

The Go client library is at the repository root. Standard Go development practices apply:

go mod tidy
go test ./...
go build ./...

License

Apache 2.0 - See LICENSE file for details.

Documentation

Index

Constants

View Source
const (
	SLIP44Ethereum uint32 = 60
	SLIP44Lux      uint32 = 9000
)

SLIP-0044 / BIP-44 coin types referenced from this package.

60   — Ethereum (and every EVM-compat chain ridden through it)
9000 — Lux  (registered)
View Source
const (
	EVMChainIDLuxMainnet   uint64 = 96369
	EVMChainIDHanzoMainnet uint64 = 36963
	EVMChainIDZooMainnet   uint64 = 200200
	EVMChainIDParsMainnet  uint64 = 7777 // placeholder until Pars publishes registry
)

Reserved EIP-155 chain IDs for Lux-family EVMs.

Sources: chain registries published in their own repos. These are documentation-only constants — the Ledger client passes them through to the device unmodified inside the EIP-155 tx body. Mainnets only; testnets/devnets follow the +1/+2 convention each chain publishes.

View Source
const (
	CLALux uint8 = 0x80
	CLAEVM uint8 = 0xE0
)

CLA bytes routed by the Lux Ledger app firmware.

View Source
const (
	VendorLedger         = 0x2c97
	UsagePageLedgerNanoS = 0xffa0
	Channel              = 0x0101
	PacketSize           = 64
)
View Source
const (
	CLA     = 0x80
	CLA_ETH = 0xE0

	CHUNK_SIZE = 250
	HASH_LEN   = 32

	PAYLOAD_INIT = 0x00
	PAYLOAD_ADD  = 0x01
	PAYLOAD_LAST = 0x02

	FIRST_MESSAGE = 0x01
	LAST_MESSAGE  = 0x02
	NEXT_MESSAGE  = 0x03

	P1_ONLY_RETRIEVE          = 0x00
	P1_SHOW_ADDRESS_IN_DEVICE = 0x01

	INS_GET_VERSION             = 0x00
	INS_WALLET_ID               = 0x01
	INS_GET_ADDR                = 0x02
	INS_GET_EXTENDED_PUBLIC_KEY = 0x03
	INS_SIGN_HASH               = 0x04
	INS_SIGN                    = 0x05
	INS_SIGN_MSG                = 0x06

	HARDENED = 0x80000000
)

Variables

This section is empty.

Functions

func BIP44Path added in v1.1.7

func BIP44Path(chain Chain, account, change, index uint32) string

BIP44Path returns the BIP-44 derivation path for the given chain at the given account / change / index. Hardened components are appended where BIP-44 requires them.

The path shape is:

m/44'/<slip44>'/<account>'/<change>/<index>

where slip44 is the chain's SLIP44 field (60 for EVM-compat, 9000 for Lux native). EVM-compat chains share coin type 60 — the EIP-155 chain_id in the tx body distinguishes which network the signature applies to. This is the same convention MetaMask, Ledger Live, and every multi-EVM wallet uses.

The function never returns an internal-format error: any non-nil error is a caller bug (unknown chain). Callers typically construct once at startup and reuse the string.

func BIP44PathForName added in v1.1.7

func BIP44PathForName(name string, account, change, index uint32) (string, error)

BIP44PathForName is the convenience wrapper most callers want: pass a chain name + indices, get a path back. Unknown chain → explicit error. Used by mpcd / mpc CLI / KMS bridge for path selection.

func CheckVersion

func CheckVersion(ver VersionInfo, req VersionInfo) error

CheckVersion compares the current version with the required version

func ConcatMessageAndChangePath

func ConcatMessageAndChangePath(message []byte, path []string) []byte

func EVMPath added in v1.1.7

func EVMPath(account, change, index uint32) string

EVMPath returns the standard 5-element BIP-44 path for an EVM-compat chain. Chain ID is captured separately (in the EIP-155 tx body); the derivation path itself is identical across EVM-compat siblings, so the same Ledger key signs for Hanzo / Zoo / Pars / Lux C-Chain.

m/44'/60'/<account>'/<change>/<index>

This is intentional: a single hardware key roams across every EVM chain in the family. Replay protection comes from chain_id in the signed payload, not from a different derivation path per chain.

func LuxNativePath added in v1.1.7

func LuxNativePath(account uint32) string

LuxNativePath returns the native Lux P/X-Chain prefix path

m/44'/9000'/<account>'

suitable as the rootPath argument to LedgerLux.SignFull / LedgerLux.SignHashFull. The signing API expects a 3-element prefix with hardened account; the suffix paths (change / index) are passed separately as signers/changePaths.

This helper is the one and only way to derive that prefix — call sites must NOT format the string themselves.

func NewVersionRequiredError

func NewVersionRequiredError(req VersionInfo, ver VersionInfo) error

func RemoveDuplicates

func RemoveDuplicates(elements []string) []string

func SerializeChainID

func SerializeChainID(chainID string) ([]byte, error)

SerializeChainID serializes a chain ID into a byte slice

func SerializeHrp

func SerializeHrp(hrp string) ([]byte, error)

SerializeHrp serializes an HRP into a byte slice

func SerializePath

func SerializePath(path string) ([]byte, error)

func SerializePathSuffix

func SerializePathSuffix(path string) ([]byte, error)

func UnwrapResponseAPDU added in v1.1.0

func UnwrapResponseAPDU(channel uint16, packet []byte, packetSize int) ([]byte, bool)

UnwrapResponseAPDU processes a packet from HID transport and returns the payload

func VerifySignature

func VerifySignature(pubkey, hash, signature []byte) bool

VerifySignature checks that the given public key created signature over hash. The public key should be in compressed (33 bytes) or uncompressed (65 bytes) format. The signature should have the 64 byte [R || S] format.

func WrapCommandAPDU added in v1.1.0

func WrapCommandAPDU(channel uint16, command []byte, packetSize int) ([][]byte, error)

WrapCommandAPDU turns the command into a sequence of 64 byte packets for HID transport

Types

type Chain added in v1.1.7

type Chain struct {
	// Name is the canonical short name ("lux", "hanzo", "zoo", "pars").
	// Used as the dictionary key — lower-case, no spaces.
	Name string

	// SLIP44 is the BIP-44 / SLIP-0044 coin type for native-coin paths.
	//
	// Lux owns 9000. EVM-compatible siblings reuse 60 (Ethereum) so
	// existing wallets discover them without a SLIP-0044 application.
	// A chain that wants its own SLIP-0044 must register via
	// satoshilabs/slips#44; until then they ride 60 with chain_id
	// disambiguation in the tx body.
	SLIP44 uint32

	// EVMChainID is the EIP-155 numeric chain_id for the chain's EVM.
	// Zero if the chain has no EVM (Lux P/X chains use SLIP44=9000 and
	// the native CLA=0x80 path; this field stays 0 for those).
	EVMChainID uint64

	// HRP is the Bech32 human-readable prefix for native-format
	// addresses ("lux" → "P-lux1...", "hanzo" → "P-hanzo1...", etc.).
	// Empty if the chain only addresses via 0x.
	HRP string

	// CLA is the device APDU class byte. CLAEVM (0xE0) routes through
	// the Ethereum-compat instruction set; CLALux (0x80) routes through
	// the Lux native-chain instruction set.
	CLA uint8

	// AddressFormat documents the on-chain address shape so callers
	// (operator scripts, audit tools) don't have to grep for it.
	//
	//   "bech32"  → P-lux1tlq..., X-lux1...
	//   "evm"     → 0x...
	//   "both"    → either, depending on path coin type
	AddressFormat string
}

Chain is a single network the Ledger client knows how to address.

One Ledger app, many chains. The on-device firmware ships two CLAs:

CLA = 0x80  — Lux native instructions (P/X/C/Q/Z/A/B/M/F-chain ops,
              Bech32 + cb58 addresses, validator/delegator flows)
CLA = 0xE0  — Ethereum-compatible instructions (any EVM chain;
              EIP-155 tx body carries the chain_id; firmware shows
              the integer chain_id alongside the recipient + value)

Every Hanzo / Lux / Zoo / Pars chain that exposes an EVM is reachable today through CLA=0xE0. The Go client picks the correct BIP44 derivation path from this registry; the device firmware does not have to change.

The on-device "[Hanzo] Transfer" badge UX (chain-aware label above the EVM summary) is a v0.2.0 firmware enhancement — see docs/CHAIN_PATHS.md. Until that ships, the user sees the standard Ethereum-app review screen with chain_id + to + value + data.

func Chains added in v1.1.7

func Chains() []Chain

Chains returns the canonical list of supported chains, sorted by name. The slice is a fresh copy; callers may not mutate the registry.

func Lookup added in v1.1.7

func Lookup(name string) (Chain, error)

Lookup returns the chain metadata for the given name. Names are matched case-insensitively. Unknown names produce an explicit error — callers MUST NOT default to "lux" silently.

type LedgerAdmin added in v1.1.0

type LedgerAdmin interface {
	CountDevices() int
	ListDevices() ([]string, error)
	Connect(deviceIndex int) (LedgerDevice, error)
}

LedgerAdmin defines the interface for managing Ledger devices.

func NewLedgerAdmin added in v1.1.0

func NewLedgerAdmin() LedgerAdmin

type LedgerAdminHID added in v1.1.0

type LedgerAdminHID struct{}

func (*LedgerAdminHID) Connect added in v1.1.0

func (admin *LedgerAdminHID) Connect(deviceIndex int) (LedgerDevice, error)

func (*LedgerAdminHID) CountDevices added in v1.1.0

func (admin *LedgerAdminHID) CountDevices() int

func (*LedgerAdminHID) ListDevices added in v1.1.0

func (admin *LedgerAdminHID) ListDevices() ([]string, error)

type LedgerDevice added in v1.1.0

type LedgerDevice interface {
	Exchange(command []byte) ([]byte, error)
	Close() error
}

LedgerDevice defines the interface for interacting with a Ledger device.

type LedgerDeviceHID added in v1.1.0

type LedgerDeviceHID struct {
	// contains filtered or unexported fields
}

func (*LedgerDeviceHID) Close added in v1.1.0

func (ledger *LedgerDeviceHID) Close() error

func (*LedgerDeviceHID) Exchange added in v1.1.0

func (ledger *LedgerDeviceHID) Exchange(command []byte) ([]byte, error)

func (*LedgerDeviceHID) Read added in v1.1.0

func (ledger *LedgerDeviceHID) Read() <-chan []byte

type LedgerError

type LedgerError int
const (
	U2FUnknown                  LedgerError = 1
	U2FBadRequest               LedgerError = 2
	U2FConfigurationUnsupported LedgerError = 3
	U2FDeviceIneligible         LedgerError = 4
	U2FTimeout                  LedgerError = 5
	Timeout                     LedgerError = 14
	NoErrors                    LedgerError = 0x9000
	DeviceIsBusy                LedgerError = 0x9001
	ErrorDerivingKeys           LedgerError = 0x6802
	ExecutionError              LedgerError = 0x6400
	WrongLength                 LedgerError = 0x6700
	EmptyBuffer                 LedgerError = 0x6982
	OutputBufferTooSmall        LedgerError = 0x6983
	DataIsInvalid               LedgerError = 0x6a80
	ConditionsNotSatisfied      LedgerError = 0x6985
	TransactionRejected         LedgerError = 0x6986
	BadKeyHandle                LedgerError = 0x6a81
	InvalidP1P2                 LedgerError = 0x6b00
	InstructionNotSupported     LedgerError = 0x6d00
	AppDoesNotSeemToBeOpen      LedgerError = 0x6e01
	UnknownError                LedgerError = 0x6f00
	SignVerifyError             LedgerError = 0x6f01
)

type LedgerLux

type LedgerLux struct {
	// contains filtered or unexported fields
}

LedgerLux represents a connection to the Lux app in a Ledger device

func FindLedgerLuxApp

func FindLedgerLuxApp() (_ *LedgerLux, rerr error)

FindLedgerLuxApp FindLedgerLuxUserApp finds a Lux user app running in a ledger device

func NewLedger added in v1.1.3

func NewLedger() (*LedgerLux, error)

NewLedger creates a new LedgerLux that implements the keychain.Ledger interface. This is the recommended way to get a Ledger for use with the keychain package.

func (*LedgerLux) Address added in v1.1.5

func (l *LedgerLux) Address(displayHRP string, addressIndex uint32) (ids.ShortID, error)

Address returns the address at the given index (implements keychain.Ledger)

func (*LedgerLux) CheckVersion

func (ledger *LedgerLux) CheckVersion(ver VersionInfo) error

CheckVersion returns true if the App version is supported by this library

func (*LedgerLux) Close

func (ledger *LedgerLux) Close() error

Close closes a connection with the Lux user app

func (*LedgerLux) Disconnect added in v1.1.5

func (l *LedgerLux) Disconnect() error

Disconnect closes the ledger connection (implements keychain.Ledger)

func (*LedgerLux) GetAddresses added in v1.1.5

func (l *LedgerLux) GetAddresses(addressIndices []uint32) ([]ids.ShortID, error)

GetAddresses returns the addresses for multiple indices (implements keychain.Ledger)

func (*LedgerLux) GetPubKey

func (ledger *LedgerLux) GetPubKey(path string, show bool, hrp string, chainid string) (*ResponseAddr, error)

GetPubKey returns the pubkey and hash

func (*LedgerLux) GetVersion

func (ledger *LedgerLux) GetVersion() (*VersionInfo, error)

GetVersion returns the current version of the Lux user app

func (*LedgerLux) Sign

func (l *LedgerLux) Sign(message []byte, addressIndex uint32) ([]byte, error)

Sign signs a message at the given address index (implements keychain.Ledger)

func (*LedgerLux) SignFull added in v1.1.5

func (ledger *LedgerLux) SignFull(pathPrefix string, signingPaths []string, message []byte, changePaths []string) (*ResponseSign, error)

func (*LedgerLux) SignHash

func (l *LedgerLux) SignHash(hash []byte, addressIndex uint32) ([]byte, error)

SignHash signs a hash at the given address index (implements keychain.Ledger)

func (*LedgerLux) SignHashFull added in v1.1.5

func (ledger *LedgerLux) SignHashFull(pathPrefix string, signingPaths []string, hash []byte) (*ResponseSign, error)

func (*LedgerLux) SignTransaction added in v1.1.5

func (l *LedgerLux) SignTransaction(rawUnsignedHash []byte, addressIndices []uint32) ([][]byte, error)

SignTransaction signs a transaction hash with multiple addresses (implements keychain.Ledger)

func (*LedgerLux) VerifyMultipleSignatures

func (ledger *LedgerLux) VerifyMultipleSignatures(response ResponseSign, messageHash []byte, rootPath string, signingPaths []string, hrp string, chainID string) error

type ResponseAddr

type ResponseAddr struct {
	PublicKey []byte
	Hash      []byte
	Address   string
}

type ResponseSign

type ResponseSign struct {
	Hash      []byte
	Signature map[string][]byte
}

func SignAndCollect

func SignAndCollect(signingPaths []string, ledger *LedgerLux) (*ResponseSign, error)

type VersionInfo

type VersionInfo struct {
	AppMode uint8
	Major   uint8
	Minor   uint8
	Patch   uint8
}

VersionInfo contains app version information

func (VersionInfo) String

func (c VersionInfo) String() string

type VersionRequiredError

type VersionRequiredError struct {
	Found    VersionInfo
	Required VersionInfo
}

func (VersionRequiredError) Error

func (e VersionRequiredError) Error() string

Directories

Path Synopsis
go module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL