From a8f12991c14b45c8f480f92ae1f26a6353d512c6 Mon Sep 17 00:00:00 2001 From: Boris Oncev Date: Wed, 25 Feb 2026 16:50:43 +0700 Subject: [PATCH] Add technical specification --- doc/mintlayer.md | 215 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 doc/mintlayer.md diff --git a/doc/mintlayer.md b/doc/mintlayer.md new file mode 100644 index 0000000..97f1c60 --- /dev/null +++ b/doc/mintlayer.md @@ -0,0 +1,215 @@ +# Mintlayer application: Technical Specifications + +This page details the protocol implemented for the Mintlayer Ledger app. + +## Framework + +### APDUs + +The messaging format of the app is compatible with the standard [APDU protocol](https://developers.ledger.com/docs/device-app/explanation/io#apdu-interpretation-loop). + +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 | + +### Chunking and P2 + +The APDU buffer on the Ledger device has a maximum data length (`MAX_ADPU_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 of `1020` bytes) and returns `0x9000` (Success) to ask the client for the next chunk. + +_Note: For chunked commands, `INS` and `P1` must remain identical across all chunks of the same sequence._ + +### Data Serialization + +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. + +## Status Words + +The application returns standard Ledger status words as well as app-specific and ECC cryptographic errors. + +| SW | SW name | Description | +| ------ | ------------------------------ | ---------------------------------------------- | +| 0x9000 | `Ok` | Success | +| 0x6700 | `WrongApduLength` | Incorrect APDU length | +| 0x6985 | `Deny` | Rejected by user / User cancelled | +| 0x6B00 | `WrongP1P2` | Either `P1` or `P2` is incorrect | +| 0x6D00 | `InsNotSupported` | No command exists with `INS` | +| 0x6E00 | `ClaNotSupported` | Bad `CLA` used for this application | +| 0xB000 | `TxDisplayFail` | Transaction display failed | +| 0xB001 | `AddrDisplayFail` | Address display failed | +| 0xB002 | `TxWrongLength` | Transaction wrong length | +| 0xB003 | `TxParsingFail` | Transaction parsing failed | +| 0xB004 | `TxHashFail` | Transaction hashing failed | +| 0xB005 | `TxAddressFail` | Transaction address failed | +| 0xB006 | `TxSignFail` | Transaction signing failed | +| 0xB007 | `KeyDeriveFail` | Key derivation failed | +| 0xB008 | `VersionParsingFail` | Version parsing failed | +| 0xB009 | `WrongContext` | Wrong context (e.g., Next called before Start) | +| 0xB00A | `DeserializeFail` | SCALE deserialization failed | +| 0xB00B | `TxInvalidInputUtxo` | Invalid input UTXO | +| 0xB00C | `TxNumericOperationFail` | Numeric operation failed | +| 0xB00D | `TxUnsupportedInput` | Unsupported input | +| 0xB00E | `TxInvalidTokenV0` | Invalid Token V0 | +| 0xB00F | `TxInvalidInputPath` | Invalid input path | +| 0xB010 | `NothingToSign` | Nothing to sign | +| 0xB011 | `TxAlreadyFinished` | Transaction already finished | +| 0xB012 | `InvalidPath` | Invalid BIP32 path | +| 0xB013 | `InvalidUncompressedPublicKey` | Invalid uncompressed public key | +| 0xB014 | `MaxBufferLenExceeded` | Max buffer length exceeded (Chunking limit) | +| 0xB015 | `DifferentInputCommitmentHash` | Different input commitment hash | +| 0xB016 | `OrdersV0NotSupported` | Orders V0 not supported | +| 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 | + +--- + +## Commands + +### GET_PUBLIC_KEY + +Returns a public key and chain code at the given derivation path. +Optionally displays the generated address on the device screen for user verification. + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | -------------------- | +| E1 | 00 | `0` or `1` (Display) | + +**Input data (`PublicKeyReq` - SCALE encoded)** + +| Type | Name | Description | +| ----------- | ----------- | --------------------------------------------------------- | +| `u8` (Enum) | `coin_type` | `0` = Mainnet, `1` = Testnet, `2` = Regtest, `3` = Signet | +| `Vec` | `path` | The BIP32 derivation path | + +**Output data (`GetPublicKeyRespones` - SCALE encoded)** + +| Length | Description | +| ------ | --------------------------- | +| `65` | The uncompressed public key | +| `32` | The chain code | + +#### Description + +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). + +--- + +### SIGN_TX + +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). + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | ------------------------- | +| E1 | 01 | `0` (Start) or `1` (Next) | + +**Input Data for `P1 = 0` (Start) (`TxMetadataReq` - SCALE encoded)** + +| Type | Name | Description | +| ----- | ------------- | --------------------------------------------------------- | +| `u8` | `coin` | `0` = Mainnet, `1` = Testnet, `2` = Regtest, `3` = Signet | +| `u8` | `version` | Transaction version | +| `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) (`SignTxReq` Enum - SCALE encoded)** + +The client sends a sequence of `SignTxReq` variants. The variant index dictates the type of data being sent: + +- `Input` (Index 0): Contains `TxInputReq` (Input address paths and UTXO/Account info). +- `InputCommitment` (Index 1): Contains `SighashInputCommitment`. +- `Output` (Index 2): Contains `TxOutputReq`. +- `NextSignature` (Index 3): Requests the device to yield the next available signature. + +**Output data (`SignatureResponse` - SCALE encoded)** + +Yielded during `NextSignature` sequences. + +| Type | Name | Description | +| ------------- | -------------- | ------------------------------------------- | +| `[u8; 64]` | `signature` | The 64-byte cryptographic signature | +| `Option` | `multisig_idx` | Optional multisig index | +| `u32` | `input_idx` | The index of the input that was just signed | +| `bool` | `has_next` | True if there are more signatures remaining | + +#### Description + +To sign a transaction, the client must follow a strict order: + +1. Call `SIGN_TX` with `P1 = 0` (Start) passing the overall transaction metadata (`TxMetadataReq`). +2. Sequentially call `SIGN_TX` with `P1 = 1` (Next) to stream inputs `Input`, input commitments `InputCommitment` and then outputs (`Output`). +3. After all data is verified by the user on the device's secure screen, the client requests signatures by repeatedly calling `SIGN_TX` with `P1 = 1` and the `NextSignature` variant. +4. The device will yield `SignatureResponse` payloads until `has_next` is false. + +--- + +### SIGN_MSG + +Signs a generic message using a BIP-32 derived key. The process is stateful to allow streaming long messages. + +#### Encoding + +**Command** + +| _CLA_ | _INS_ | _P1_ | +| ----- | ----- | ------------------------- | +| E1 | 02 | `0` (Start) or `1` (Next) | + +**Input Data for `P1 = 0` (Start) (`SignMessageReq` - SCALE encoded)** + +| Type | Name | Description | +| ----------- | ----------- | --------------------------------------------- | +| `u8` (Enum) | `coin` | Coin type (Mainnet, Testnet, Regtest, Signet) | +| `u8` (Enum) | `addr_type` | `0` = PublicKey, `1` = PublicKeyHash | +| `Vec` | `path` | The BIP32 derivation path to use for signing | + +**Input Data for `P1 = 1` (Next)** + +| Type | Description | +| ------------ | ------------------------------------------------------------- | +| `` | The raw byte chunks of the message payload to be accumulated. | + +**Output data (`MsgSignature` - SCALE encoded)** + +| Length | Description | +| ------ | ----------------------------------- | +| `64` | The 64-byte cryptographic signature | + +#### Description + +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.