Lux App
General structure
The app provides support for 2 sets of instructions, the Lux set and the Ethereum set.
The Ethereum set is aimed at providing compatibility with EVM wallets,
as such the API is the same as the Ethereum App, with the CLA being 0xE0,
whilst the Lux app normally uses 0x80 for the CLA.
The general structure of commands and responses is as follows:
Commands
| Field |
Type |
Content |
Note |
| CLA |
byte (1) |
Application Identifier |
0x80/0xE0 |
| INS |
byte (1) |
Instruction ID |
|
| P1 |
byte (1) |
Parameter 1 |
|
| P2 |
byte (1) |
Parameter 2 |
|
| L |
byte (1) |
Bytes in payload |
|
| PAYLOAD |
byte (L) |
Payload |
|
Response
| Field |
Type |
Content |
Note |
| ANSWER |
byte (?) |
Answer |
depends on the command |
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
Return codes
| Return code |
Description |
| 0x6400 |
Execution Error |
| 0x6700 |
Wrong Length |
| 0x6982 |
Empty buffer |
| 0x6983 |
Output buffer too small |
| 0x6A80 |
Data Invalid |
| 0x6985 |
Conditions not satisfied |
| 0x6986 |
Command not allowed |
| 0x6B00 |
Invalid P1/P2 |
| 0x6D00 |
INS not supported |
| 0x6E00 |
CLA not supported |
| 0x6F00 |
Unknown |
| 0x9000 |
Success |
| 0x9001 |
Busy |
Command definition
INS_GET_VERSION
Command
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x00 |
| P1 |
byte (1) |
Parameter 1 |
ignored |
| P2 |
byte (1) |
Parameter 2 |
ignored |
| L |
byte (1) |
Bytes in payload |
ignored |
Response
| Field |
Type |
Content |
Note |
| TEST |
byte (1) |
Test Mode |
0xFF means test mode is enabled |
| MAJOR |
byte (1) |
Version Major |
|
| MINOR |
byte (1) |
Version Minor |
|
| PATCH |
byte (1) |
Version Patch |
|
| LOCKED |
byte (1) |
Device is locked |
|
| TARGET ID |
byte (4) |
Target ID |
|
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
INS_GET_WALLET_ID
Command
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x01 |
| P1 |
byte (1) |
Request User confirmation |
No = 0 |
| P2 |
byte (1) |
Parameter 2 |
ignored |
| L |
byte (1) |
Bytes in payload |
ignored |
Response
| Field |
Type |
Content |
Note |
| WALLET_ID |
byte (6) |
Wallet ID |
|
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
INS_GET_PUBLIC_KEY
Command
If the HRPLen is 0, then the default HRP of 'lux' is used.
If the ChainIDLen is 0, then the default ChainID of 32 zero bytes (P-Chain's)
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x02 |
| P1 |
byte (1) |
Request User confirmation |
No = 0 |
| P2 |
byte (1) |
|
ignored |
| L |
byte (1) |
Bytes in payload |
(depends) |
| HRPLen |
byte (1) |
Length of HRP |
0 to 24 |
| HRP |
byte (HRPLen) |
HRP |
? |
| ChainIDLen |
byte (1) |
Length of ChainID |
0 OR 32 |
| ChainID |
byte (ChainIDLen) |
ChainID |
? |
| PathN |
byte (1) |
Number of path components |
? (typically 4, up to 6) |
| Path[0] |
byte (4) |
Derivation Path Data |
0x8000002c |
| Path[1] |
byte (4) |
Derivation Path Data |
0x80002328 |
| Path[2] |
byte (4) |
Derivation Path Data |
? |
| Path[3] |
byte (4) |
Derivation Path Data |
? |
| Path[4] |
byte (4) |
Derivation Path Data |
? |
| Path[5] |
byte (4) |
Derivation Path Data |
? |
Response
| Field |
Type |
Content |
Note |
| PK_LEN |
byte (1) |
Bytes in PKEY |
|
| PKEY |
byte (??) |
Public key bytes |
Compressed public key |
| PKEY_HASH |
byte (20) |
Public key hash |
Ripemd160(Sha256(PKEY)) |
| ADDR |
byte (??) |
Address |
CB58 encoded address |
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
INS_GET_EXTENDED_PUBLIC_KEY
Command
If the HRPLen is 0, then the default HRP of 'lux' is used.
If the ChainIDLen is 0, then the default ChainID of 32 zero bytes (P-Chain's)
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x03 |
| P1 |
byte (1) |
Request User confirmation |
No = 0 |
| P2 |
byte (1) |
|
ignored |
| L |
byte (1) |
Bytes in payload |
(depends) |
| HRPLen |
byte (1) |
Length of HRP |
0 to 24 |
| HRP |
byte (HRPLen) |
HRP |
? |
| ChainIDLen |
byte (1) |
Length of ChainID |
0 OR 32 |
| ChainID |
byte (ChainIDLen) |
ChainID |
? |
| PathN |
byte (1) |
Number of path components |
? (typically 4, up to 6) |
| Path[0] |
byte (4) |
Derivation Path Data |
0x8000002c |
| Path[1] |
byte (4) |
Derivation Path Data |
0x80002328 |
| Path[2] |
byte (4) |
Derivation Path Data |
? |
| Path[3] |
byte (4) |
Derivation Path Data |
? |
| Path[4] |
byte (4) |
Derivation Path Data |
? |
| Path[5] |
byte (4) |
Derivation Path Data |
? |
Response
| Field |
Type |
Content |
Note |
| PK_LEN |
byte (1) |
Bytes in PKEY |
|
| PKEY |
byte (??) |
Public key bytes |
Compressed public key |
| CHAIN_CODE |
byte (32) |
Chain Code |
|
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
INS_SIGN_HASH
The app includes a protocol to sign the same message multiple times, as described in this instruction.
Command
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x04 |
| P1 |
byte (1) |
Signature step |
0 = init |
|
|
|
x = next |
|
|
|
2 = last |
| P2 |
byte (1) |
|
ignored |
| L |
byte (1) |
Bytes in payload |
(depends) |
Init
The first message should contain the root path for the keys to use, as well as the hash to sign.
| Field |
Type |
Content |
Expected |
| PathN |
byte (1) |
Number of path components |
3 |
| Path[0] |
byte (4) |
Derivation Path Data |
0x8000002c |
| Path[1] |
byte (4) |
Derivation Path Data |
0x80002328 |
| Path[2] |
byte (4) |
Derivation Path Data |
0x80000000 |
| Hash |
byte (32) |
Hash to sign |
? |
Next
The next N messages should contain the last 2 path elements needed to compute the private key
| Field |
Type |
Content |
Expected |
| PathN |
byte (1) |
Number of path components |
2 |
| Path[0] |
byte (4) |
Derivation Path Data |
? |
| Path[1] |
byte (4) |
Derivation Path Data |
? |
Last
Same as next, but also signals the app that no more signatures are to be produced
Response
Other than Init, which just returns a success code, Next and Last have the following response:
| Field |
Type |
Content |
Note |
| SIG |
byte (65) |
Signature |
signature |
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
INS_SIGN
The app includes a protocol to upload a large payload in multiple messages, as described in this instruction.
Command
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x05 |
| P1 |
byte (1) |
Payload desc |
0 = init |
|
|
|
1 = next |
|
|
|
2 = last |
| P2 |
byte (1) |
|
ignored |
| L |
byte (1) |
Bytes in payload |
(depends) |
The first packet/chunk includes the root derivation path.
Init
| Field |
Type |
Content |
Expected |
| PathN |
byte (1) |
Number of path components |
3 |
| Path[0] |
byte (4) |
Derivation Path Data |
0x8000002c |
| Path[1] |
byte (4) |
Derivation Path Data |
0x80002328 |
| Path[2] |
byte (4) |
Derivation Path Data |
? |
Add
| Field |
Type |
Content |
Expected |
| ... |
... |
... |
|
| P1 |
byte (1) |
Payload desc |
1 |
| ... |
... |
... |
|
| Message |
bytes |
payload |
|
The message payload is expected to be prefixed with a list of change paths, once, in the following format.
Any byte after is understood to be part of the message to sign and will be parsed accordingly.
| Field |
Type |
Content |
Expected |
| ChangePathN |
byte (4) |
Number of change paths |
|
| ChangePathN-1 |
byte (1) |
Number of path components |
2 |
| Path[0] |
byte (4) |
Derivation Path Data |
? |
| Path[1] |
byte (4) |
Derivation Path Data |
? |
| ChangePathN-2 |
byte (1) |
Number of path components |
2 |
| Path[0] |
byte (4) |
Derivation Path Data |
? |
| Path[1] |
byte (4) |
Derivation Path Data |
|
| ... |
... |
... |
|
Last
This signals the app that no more data should be received in regards to this payload.
This will trigger the UI confirmation flow,
storing the confirmed hash for signing later via [INS_SIGN_HASH], skipping the "Init" step.
| Field |
Type |
Content |
Expected |
| ... |
... |
... |
|
| P1 |
byte (1) |
Payload desc |
2 |
| ... |
... |
... |
|
| Data |
bytes |
Remaining data to sign |
|
Response
| Field |
Type |
Content |
Note |
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
Multi-chain dispatch (today)
The current firmware exposes two CLA bytes:
| CLA |
Family |
Used by |
| 0x80 |
Lux native operations |
Lux P/X chains (validator/delegator) |
| 0xE0 |
Ethereum-compat |
Lux C-Chain, Hanzo, Zoo, Pars |
Every EVM-compat chain in the family signs through CLA=0xE0. The
network is identified by the EIP-155 chain_id field inside the
serialized RLP transaction body — the firmware does not have to know
about each chain individually.
The Go client picks the derivation path via
github.com/luxfi/ledger:
See CHAIN_PATHS.md for the full mapping.
INS_SIGN_MSG
Used to sign an lux personal message.
The payload should include the header, please see the lux docs for more information.
Uses the protocol to upload a large payload with multiple messages.
Command
| Field |
Type |
Content |
Expected |
| CLA |
byte (1) |
Application Identifier |
0x80 |
| INS |
byte (1) |
Instruction ID |
0x06 |
| P1 |
byte (1) |
Payload desc |
0 = init |
|
|
|
1 = next |
|
|
|
2 = last |
| P2 |
byte (1) |
|
ignored |
| L |
byte (1) |
Bytes in payload |
(depends) |
The first packet/chunk includes the root derivation path.
Init
| Field |
Type |
Content |
Expected |
| PathN |
byte (1) |
Number of path components |
3 |
| Path[0] |
byte (4) |
Derivation Path Data |
0x8000002c |
| Path[1] |
byte (4) |
Derivation Path Data |
0x80002328 |
| Path[2] |
byte (4) |
Derivation Path Data |
? |
Add
| Field |
Type |
Content |
Expected |
| ... |
... |
... |
|
| P1 |
byte (1) |
Payload desc |
1 |
| ... |
... |
... |
|
| Message |
bytes |
message to sign |
|
Last
This signals the app that no more data should be received in regards to this payload.
This will trigger the UI confirmation flow,
storing the confirmed hash for signing later via [INS_SIGN_HASH], skipping the "Init" step.
| Field |
Type |
Content |
Expected |
| ... |
... |
... |
|
| P1 |
byte (1) |
Payload desc |
2 |
| ... |
... |
... |
|
| Data |
bytes |
Remaining data to sign |
|
Response
| Field |
Type |
Content |
Note |
| SW1-SW2 |
byte (2) |
Return code |
see list of return codes |
Per-chain UI badge (firmware v0.2.0, future)
The shipping firmware shows the standard Ethereum-app review screen
for every CLA 0xE0 transaction: integer chain_id, recipient,
value, data. This is sufficient for safety — the user verifies the
chain by chain_id — but does not display a friendly chain name
("[Hanzo] Transfer", "[Zoo] Mint NFT", …).
A v0.2.0 firmware change adds a chain-id table compiled into the
binary that maps chain_id → display name and renders the name as a
badge above the operation summary. The wire protocol stays unchanged;
existing wallets remain compatible.
When the table-driven badge ships, this spec will document the
optional registration message:
This is a follow-on. The current Go client and HSM bridge work
without it.