or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

apdu-communication.mdconfiguration.mddevice-management.mderror-handling.mdevents-lifecycle.mdindex.md
tile.json

apdu-communication.mddocs/

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