This page details the protocol implemented for the Mintlayer Ledger app.
The messaging format of the app is compatible with the standard APDU protocol.
The class byte used for all standard commands is CLA = 0xE1.
| CLA | INS | COMMAND NAME | DESCRIPTION |
|---|---|---|---|
| E1 | 00 | GET_PUBLIC_KEY | Return (and optionally show on screen) a public key and chain code |
| E1 | 01 | SIGN_TX | Sign a transaction through a multi-step process |
| E1 | 02 | SIGN_MSG | Sign a generic message |
| E1 | 03 | PING | Simple ping request to check connectivity |
The APDU buffer on the Ledger device has a maximum data length (MAX_APDU_DATA_LEN = 255 bytes). To accommodate larger payloads, the app implements a chunking mechanism controlled by the P2 parameter:
P2 = 0x00(P2_DONE): This is the final chunk (or the only chunk) of the instruction. The app will assemble the buffer and execute the command.P2 = 0x80(P2_MORE): More chunks follow. The app accumulates the data into a buffer (up to a maximum of4080bytes) and returns SCALE-encodedResponse::ExpectingNextChunkto ask the client for the next chunk.
Note: For chunked commands, INS and P1 must remain identical across all chunks of the same sequence.
The app uses SCALE codec (parity_scale_codec) for the serialization and deserialization of the data payloads. Vector length prefixes and standard variable-length integer types are represented using SCALE's Compact encoding.
On a successful command execution (returning status word 0x9000), the app encodes the returned payload inside a top-level SCALE-encoded Response enum.
The SCALE representation of an enum begins with a single byte representing the variant index, followed by the encoded fields of that variant.
The Response enum structure and its variant indices are:
| Index | Name | Inner Payload Type | Description |
|---|---|---|---|
0 |
ExpectingNextChunk |
None | Returned when more APDU chunks are expected (P2_MORE sequence) |
1 |
PublicKey |
PublicKeyResponse |
Public key and chain code response |
2 |
TxSetup |
None | Acknowledges transaction initialization (SIGN_TX with P1 = 0) |
3 |
TxNext |
None | Acknowledges receipt of a transaction chunk |
4 |
TxInputSignature |
TxInputSignatureResponse |
Contains an input signature |
5 |
MessageSetup |
None | Acknowledges message signing initialization (SIGN_MSG with P1 = 0) |
6 |
MessageSignature |
MsgSignatureResponse |
Contains the final message signature |
7 |
Pong |
None | Pong response for the PING instruction |
Any successful command output described below is prefixed by its corresponding 1-byte variant index.
The application returns standard Ledger status words as well as app-specific and ECC cryptographic errors.
| SW | SW name | Description |
|---|---|---|
| 0x5515 | DeviceLocked |
Device locked |
| 0x6982 | NothingReceived |
Nothing received |
| 0x6985 | Deny |
Rejected by user / User cancelled |
| 0x6D00 | Unknown |
Unknown |
| 0x6E00 | ClaNotSupported |
CLA not supported |
| 0x6E01 | InsNotSupported |
Instruction not supported |
| 0x6E02 | WrongP1P2 |
Wrong P1/P2 parameters |
| 0x6E03 | WrongApduLength |
Wrong APDU length |
| 0x9000 | Ok |
Success |
| 0xB000 | TxDisplayFail |
Transaction display failed |
| 0xB001 | TxLockTimeInvalid |
Transaction lock time value is invalid |
| 0xB002 | TxWrongLength |
Transaction wrong length |
| 0xB003 | HashFail |
Hashing failed |
| 0xB004 | AddressEncodingFail |
Address encoding failed |
| 0xB005 | WrongInstruction |
Different instruction than expected |
| 0xB006 | KeyDeriveFail |
Key derivation failed |
| 0xB007 | OrdersV0NotSupported |
Orders V0 not supported |
| 0xB008 | WrongContext |
Wrong context (e.g., Next called before Start) |
| 0xB009 | DeserializeFail |
SCALE deserialization failed |
| 0xB00A | TxInvalidInputUtxo |
Invalid input UTXO |
| 0xB00B | TxNumericOperationFail |
Numeric operation failed |
| 0xB00C | TxFeeUnderflow |
Tx fee underflow |
| 0xB00D | InvalidData |
Invalid data |
| 0xB00E | NothingToSign |
Nothing to sign |
| 0xB00F | TxAlreadyFinished |
Transaction already finished |
| 0xB010 | InvalidPath |
Invalid BIP32 path |
| 0xB011 | InvalidUncompressedPublicKey |
Invalid uncompressed public key |
| 0xB012 | MaxBufferLenExceeded |
Max buffer length exceeded (Chunking limit) |
| 0xB013 | DifferentInputCommitmentHash |
Different input commitment hash |
| 0xB014 | InvalidTimestamp |
Invalid timestamp |
| 0xB015 | FillOrderSigRequested |
Signature for FillOrder input requested |
| 0xB016 | TxWithZeroInputs |
Transaction has zero inputs |
| 0xB017 | TxWithMultipleReviewableInputs |
Transaction has multiple reviewable inputs |
| 0xB018 | WrongChangeOutputType |
Wrong change output type |
| 0xB019 | WrongChangeOutputDestination |
Wrong change output destination |
| 0xB01A | MismatchedChangeOutputDestination |
Mismatched change output destination |
| 0xB100 | EccCarry |
ECC Carry |
| 0xB101 | EccLocked |
ECC Locked |
| 0xB102 | EccUnlocked |
ECC Unlocked |
| 0xB103 | EccNotLocked |
ECC Not Locked |
| 0xB104 | EccNotUnlocked |
ECC Not Unlocked |
| 0xB105 | EccInternalError |
ECC Internal Error |
| 0xB106 | EccInvalidParameterSize |
ECC Invalid Parameter Size |
| 0xB107 | EccInvalidParameterValue |
ECC Invalid Parameter Value |
| 0xB108 | EccInvalidParameter |
ECC Invalid Parameter |
| 0xB109 | EccNotInvertible |
ECC Not Invertible |
| 0xB10A | EccOverflow |
ECC Overflow |
| 0xB10B | EccMemoryFull |
ECC Memory Full |
| 0xB10C | EccNoResidue |
ECC No Residue |
| 0xB10D | EccPointAtInfinity |
ECC Point At Infinity |
| 0xB10E | EccInvalidPoint |
ECC Invalid Point |
| 0xB10F | EccInvalidCurve |
ECC Invalid Curve |
| 0xB110 | EccGenericError |
ECC Generic Error |
| 0xE000 | Panic |
Panic |
Returns a public key and chain code at the given derivation path. Optionally displays the generated address on the device screen for user verification.
Command
| CLA | INS | P1 |
|---|---|---|
| E1 | 00 | 0 or 1 (Display) |
Input data (GetPubKeyReq - SCALE encoded)
| Type | Name | Description |
|---|---|---|
u8 (Enum) |
coin_type |
0 = Mainnet, 1 = Testnet, 2 = Regtest, 3 = Signet |
Vec<u32> |
path |
The BIP32 derivation path |
Output data (Response::PublicKey(PublicKeyResponse) - SCALE encoded)
| Length | Description |
|---|---|
1 |
Variant index byte (0x01) |
65 |
The uncompressed public key (PublicKeyResponse.public_key) |
32 |
The chain code (PublicKeyResponse.chain_code) |
If P1 is 0, the application derives the public key and returns it silently.
If P1 is 1, the application will display the address derived from the requested path on the device screen.
The command will only return Ok (0x9000) if the user approves it, otherwise, it returns Deny (0x6985).
Signs a Mintlayer transaction.
Because transactions can be larger than available APDU buffers and RAM, the parsing and signing process is split into an interactive state machine using P1 = 0 (Start) and P1 = 1 (Next).
Command
| CLA | INS | P1 |
|---|---|---|
| E1 | 01 | 0 (Start) or 1 (Next) |
Input Data for P1 = 0 (Start) (SignTxStartReq - SCALE encoded)
| Type | Name | Description |
|---|---|---|
u8 |
coin_type |
0 = Mainnet, 1 = Testnet, 2 = Regtest, 3 = Signet |
u8 |
version |
Transaction version (0 = V1) |
u32 |
num_inputs |
Total number of inputs in the transaction |
u32 |
num_outputs |
Total number of outputs in the transaction |
Input Data for P1 = 1 (Next) (SignTxNextReq Enum - SCALE encoded)
The client sends a sequence of SignTxNextReq variants. The variant index dictates the type of data being sent:
ProcessInput(Index 0): ContainsTxInputData(input address paths and UTXO/Account info).ProcessInputCommitment(Index 1): ContainsTxInputCommitmentData.ProcessOutput(Index 2): ContainsTxOutputData.ReturnNextSignature(Index 3): Requests the device to yield the next available signature.
Output data (Response::TxInputSignature(TxInputSignatureResponse) - SCALE encoded)
Yielded during ReturnNextSignature sequences.
The response payload is prefixed with the TxInputSignature variant index (0x04), followed by the TxInputSignatureResponse fields:
| Type | Name | Description |
|---|---|---|
[u8; 64] |
signature |
The 64-byte cryptographic signature |
u32 |
input_idx |
The index of the input that was just signed |
Option<u32> |
multisig_idx |
Optional multisig index |
bool |
has_next |
True if there are more signatures remaining |
Note: For Start (P1 = 0) and intermediate Next (P1 = 1) data chunks (before signatures), the app returns Response::TxSetup (variant index 0x02) and Response::TxNext (variant index 0x03) respectively, with no extra fields.
To sign a transaction, the client must follow a strict order:
- Call
SIGN_TXwithP1 = 0(Start) passingSignTxStartReq. - Sequentially call
SIGN_TXwithP1 = 1(Next) to stream inputs (ProcessInput), input commitments (ProcessInputCommitment) and then outputs (ProcessOutput). - After all data is verified by the user on the device's secure screen, the client requests signatures by repeatedly calling
SIGN_TXwithP1 = 1and theReturnNextSignaturevariant. - The device will yield
TxInputSignatureResponsepayloads untilhas_nextis false.
Signs a generic message using a BIP-32 derived key.
Command
| CLA | INS | P1 |
|---|---|---|
| E1 | 02 | 0 (Start) or 1 (Next) |
Input Data for P1 = 0 (Start) (SignMessageStartReq - SCALE encoded)
| Type | Name | Description |
|---|---|---|
u8 (Enum) |
coin_type |
Coin type (Mainnet, Testnet, Regtest, Signet) |
u8 (Enum) |
addr_type |
0 = PublicKey, 1 = PublicKeyHash |
Vec<u32> |
path |
The BIP32 derivation path to use for signing |
Input Data for P1 = 1 (Next)
| Type | Description |
|---|---|
<variable> |
The raw byte chunks of the message payload to be accumulated. |
Output data (Response::MessageSignature(MsgSignatureResponse) - SCALE encoded)
The response payload is prefixed with the MessageSignature variant index (0x06), followed by the MsgSignatureResponse fields:
| Length | Description |
|---|---|
64 |
The 64-byte cryptographic signature |
Note: For Start (P1 = 0) initialization, the app returns Response::MessageSetup (variant index 0x05) with no extra fields.
The SIGN_MSG flow initializes the signing context via Start.
The client then sends the message chunks iteratively using Next.
Once the message transmission is completed and the user validates the request on the hardware screen,
the device returns the 64-byte signature.
Chunking the payload via the P2 parameter mechanism is utilized here if the data exceeds a single APDU packet limit.
Simple ping request to check connectivity.
Command
| CLA | INS | P1 |
|---|---|---|
| E1 | 03 | 0 |
Input data
None.
Output data (Response::Pong - SCALE encoded)
| Length | Description |
|---|---|
1 |
Variant index byte (0x07) representing Response::Pong |
The client sends a PING command to verify that the app is running and responsive.
The app returns Response::Pong (success indicator) on success, and returns the home screen to the user.