0
# APDU Communication
1
2
Low-level and high-level methods for sending Application Protocol Data Units (APDUs) to Ledger devices. APDUs are the standard command format for communicating with smart cards and hardware security modules.
3
4
## Capabilities
5
6
### Low-Level APDU Exchange
7
8
Direct APDU exchange method intended for transport implementations. Application developers should prefer the `send()` method instead.
9
10
```typescript { .api }
11
/**
12
* Low level API to communicate with the device
13
* This method is for implementations to implement but should not be directly called
14
* Instead, the recommended way is to use send() method
15
* @param apdu The APDU data to send as a Buffer
16
* @returns Promise of response data as Buffer
17
*/
18
exchange(apdu: Buffer): Promise<Buffer>;
19
```
20
21
**Note**: This method is abstract in the base Transport class and must be implemented by concrete transport implementations. Applications should use `send()` instead.
22
23
### Atomic Exchange Implementation (Internal)
24
25
Internal method that ensures atomic execution of exchange operations with race condition protection and unresponsive device detection.
26
27
```javascript { .api }
28
/**
29
* Internal atomic exchange implementation with race condition protection
30
* This method wraps exchange operations to ensure atomicity and provide unresponsive detection
31
* @param f Function to execute atomically
32
* @returns Promise resolving to the function result
33
* @internal
34
*/
35
exchangeAtomicImpl(f: () => Promise<any>): Promise<any>;
36
```
37
38
**Internal Properties:**
39
40
```javascript { .api }
41
class Transport<Descriptor> {
42
/** Promise tracking current exchange operation for race condition prevention */
43
exchangeBusyPromise: Promise<void> | null = null;
44
}
45
```
46
47
**How it works:**
48
1. Checks if an exchange is already in progress (`exchangeBusyPromise`)
49
2. Throws `TransportRaceCondition` if another operation is pending
50
3. Creates a busy promise to track the operation
51
4. Sets up unresponsive timeout detection
52
5. Executes the provided function
53
6. Emits "responsive" event if device was previously unresponsive
54
7. Cleans up busy state and timeouts
55
56
This method is used internally by the `send()` method to ensure atomic operations and prevent race conditions.
57
58
### High-Level APDU Sending
59
60
Wrapper on top of `exchange()` that provides a more convenient interface for sending APDU commands with automatic status code checking and error handling.
61
62
```typescript { .api }
63
/**
64
* Wrapper on top of exchange to simplify work of the implementation
65
* @param cla Class byte (instruction class)
66
* @param ins Instruction byte (command code)
67
* @param p1 Parameter 1 byte
68
* @param p2 Parameter 2 byte
69
* @param data Data payload (optional, default: empty buffer)
70
* @param statusList List of accepted status codes (default: [0x9000])
71
* @returns Promise of response buffer (without status bytes)
72
*/
73
send(
74
cla: number,
75
ins: number,
76
p1: number,
77
p2: number,
78
data?: Buffer = Buffer.alloc(0),
79
statusList?: Array<number> = [StatusCodes.OK]
80
): Promise<Buffer>;
81
```
82
83
**Usage Examples:**
84
85
```javascript
86
import Transport, { StatusCodes } from "@ledgerhq/hw-transport";
87
88
// Assuming you have a transport instance
89
const transport = await MyTransport.create();
90
91
// Get application version (example APDU)
92
const versionResponse = await transport.send(
93
0xB0, // CLA: application class
94
0x01, // INS: get version instruction
95
0x00, // P1: parameter 1
96
0x00 // P2: parameter 2
97
// data: omitted (empty)
98
// statusList: omitted (defaults to [0x9000])
99
);
100
101
// Send data with custom status codes
102
const data = Buffer.from("hello", "utf8");
103
const response = await transport.send(
104
0xB0, // CLA
105
0x02, // INS
106
0x00, // P1
107
0x00, // P2
108
data, // Data payload
109
[StatusCodes.OK, 0x6A80] // Accept OK or INCORRECT_DATA
110
);
111
112
// Handle large data (automatically validates < 256 bytes)
113
try {
114
const largeData = Buffer.alloc(300); // This will throw an error
115
await transport.send(0xB0, 0x03, 0x00, 0x00, largeData);
116
} catch (error) {
117
if (error.id === "DataLengthTooBig") {
118
console.error("Data too large:", error.message);
119
}
120
}
121
```
122
123
### APDU Command Structure
124
125
APDUs follow the ISO 7816-4 standard structure:
126
127
```
128
| CLA | INS | P1 | P2 | Lc | Data | Le |
129
```
130
131
Where:
132
- **CLA** (Class): Instruction class (1 byte)
133
- **INS** (Instruction): Command code (1 byte)
134
- **P1, P2** (Parameters): Command parameters (1 byte each)
135
- **Lc** (Length): Data length (1 byte, automatically set by `send()`)
136
- **Data**: Command data (0-255 bytes)
137
- **Le** (Expected): Expected response length (omitted in this implementation)
138
139
### Response Structure
140
141
APDU responses contain:
142
143
```
144
| Data | SW1 | SW2 |
145
```
146
147
Where:
148
- **Data**: Response data (variable length)
149
- **SW1, SW2** (Status Words): 2-byte status code
150
151
The `send()` method automatically:
152
1. Constructs the proper APDU format
153
2. Sends via `exchange()`
154
3. Extracts the status code (SW1|SW2)
155
4. Checks against `statusList`
156
5. Returns only the data portion (without status bytes)
157
6. Throws `TransportStatusError` for unaccepted status codes
158
159
## Common APDU Patterns
160
161
### Get Device Information
162
163
```javascript
164
// Get app version
165
const version = await transport.send(0xB0, 0x01, 0x00, 0x00);
166
167
// Get device public key
168
const publicKey = await transport.send(
169
0xE0, // CLA for crypto operations
170
0x02, // INS for get public key
171
0x00, // P1: display on screen
172
0x00, // P2: return key format
173
derivationPath // BIP32 path as Buffer
174
);
175
```
176
177
### Send Transaction Data
178
179
```javascript
180
// Sign transaction in chunks
181
const chunks = splitTransactionIntoChunks(transactionData);
182
183
for (let i = 0; i < chunks.length; i++) {
184
const isFirst = i === 0;
185
const isLast = i === chunks.length - 1;
186
187
const response = await transport.send(
188
0xE0, // CLA
189
0x04, // INS for transaction signing
190
isFirst ? 0x00 : 0x80, // P1: first chunk vs continuation
191
isLast ? 0x80 : 0x00, // P2: last chunk marker
192
chunks[i] // Chunk data
193
);
194
195
if (isLast) {
196
// Final response contains signature
197
return response;
198
}
199
}
200
```
201
202
### Handle User Confirmation
203
204
```javascript
205
import { StatusCodes } from "@ledgerhq/hw-transport";
206
207
try {
208
const signature = await transport.send(
209
0xE0, 0x04, 0x01, 0x00,
210
transactionData,
211
[StatusCodes.OK, StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED]
212
);
213
214
console.log("Transaction signed:", signature);
215
216
} catch (error) {
217
if (error instanceof TransportStatusError) {
218
if (error.statusCode === StatusCodes.CONDITIONS_OF_USE_NOT_SATISFIED) {
219
console.log("User rejected the transaction");
220
} else {
221
console.error("Device error:", error.statusText);
222
}
223
}
224
}
225
```
226
227
## Error Handling
228
229
APDU communication can fail with various errors:
230
231
### Data Length Validation
232
233
```javascript
234
// Automatic validation prevents oversized commands
235
try {
236
const largeData = Buffer.alloc(256); // Exactly 256 bytes
237
await transport.send(0xB0, 0x01, 0x00, 0x00, largeData);
238
} catch (error) {
239
// TransportError with id "DataLengthTooBig"
240
console.error(error.message); // "data.length exceed 256 bytes limit. Got: 256"
241
}
242
```
243
244
### Status Code Errors
245
246
```javascript
247
import { StatusCodes, TransportStatusError } from "@ledgerhq/hw-transport";
248
249
try {
250
await transport.send(0xB0, 0xFF, 0x00, 0x00); // Invalid instruction
251
} catch (error) {
252
if (error instanceof TransportStatusError) {
253
console.log("Status code:", error.statusCode.toString(16)); // "6d00"
254
console.log("Status text:", error.statusText); // "INS_NOT_SUPPORTED"
255
console.log("Message:", error.message); // "Ledger device: Instruction not supported (0x6d00)"
256
}
257
}
258
```
259
260
### Common Status Codes
261
262
- **0x9000 (OK)**: Success
263
- **0x6700 (INCORRECT_LENGTH)**: Wrong data length
264
- **0x6982 (SECURITY_STATUS_NOT_SATISFIED)**: Device locked or access denied
265
- **0x6985 (CONDITIONS_OF_USE_NOT_SATISFIED)**: User denied the operation
266
- **0x6A80 (INCORRECT_DATA)**: Invalid data format
267
- **0x6B00 (INCORRECT_P1_P2)**: Invalid parameters
268
- **0x6D00 (INS_NOT_SUPPORTED)**: Instruction not supported
269
- **0x6E00 (CLA_NOT_SUPPORTED)**: Class not supported