@ledgerhq/hw-app-canton 0.6.0 → 0.7.0-nightly.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,9 @@
1
1
  import { openTransportReplayer, RecordStore } from "@ledgerhq/hw-transport-mocker";
2
+ import { TransportStatusError } from "@ledgerhq/errors";
2
3
  import Canton from "./Canton";
3
4
 
5
+ const PATH = "44'/6767'/0'/0'/0'";
6
+
4
7
  describe("Canton", () => {
5
8
  describe("decorateAppAPIMethods", () => {
6
9
  it("should properly decorate transport methods", async () => {
@@ -30,12 +33,12 @@ describe("Canton", () => {
30
33
  const canton = new Canton(transport);
31
34
 
32
35
  // WHEN
33
- const result = await canton.getAddress("44'/6767'/0'/0'/0'");
36
+ const result = await canton.getAddress(PATH);
34
37
 
35
38
  // THEN
36
39
  expect(result).toEqual({
37
40
  address: "canton_402f2e68",
38
- path: "44'/6767'/0'/0'/0'",
41
+ path: PATH,
39
42
  publicKey: "c59f7f29374d24506dd6490a5db472cf00958e195e146f3dc9c97f96d5c51097",
40
43
  });
41
44
  });
@@ -51,12 +54,12 @@ describe("Canton", () => {
51
54
  const canton = new Canton(transport);
52
55
 
53
56
  // WHEN
54
- const result = await canton.getAddress("44'/6767'/0'/0'/0'", true);
57
+ const result = await canton.getAddress(PATH, true);
55
58
 
56
59
  // THEN
57
60
  expect(result).toEqual({
58
61
  address: "canton_402f2e68",
59
- path: "44'/6767'/0'/0'/0'",
62
+ path: PATH,
60
63
  publicKey: "c59f7f29374d24506dd6490a5db472cf00958e195e146f3dc9c97f96d5c51097",
61
64
  });
62
65
  });
@@ -67,74 +70,218 @@ describe("Canton", () => {
67
70
  const canton = new Canton(transport);
68
71
 
69
72
  // WHEN & THEN
70
- return expect(canton.getAddress("invalid path")).rejects.toThrow();
73
+ await expect(canton.getAddress("invalid-path")).rejects.toThrow();
71
74
  });
72
75
 
73
- it("should handle various derivation paths", async () => {
76
+ it("should handle user refused address", async () => {
74
77
  // GIVEN
75
78
  const transport = await openTransportReplayer(
76
79
  RecordStore.fromString(`
77
- => e005000015058000002c80001a6f800000008000000080000001
78
- <= 205e66a10773c0860e73bb6015947806555765df5f9b5b4636df4255a57c57d702205e66a10773c0860e73bb6015947806555765df5f9b5b4636df4255a57c57d7029000
80
+ => e005010015058000002c80001a6f800000008000000080000000
81
+ <= 6985
79
82
  `),
80
83
  );
81
84
  const canton = new Canton(transport);
82
85
 
86
+ // WHEN & THEN
87
+ await expect(canton.getAddress(PATH, true)).rejects.toThrow(new TransportStatusError(0x6985));
88
+ });
89
+ });
90
+
91
+ describe("signTransaction", () => {
92
+ it("should sign untyped versioned message without challenge", async () => {
93
+ // GIVEN
94
+ const transport = await openTransportReplayer(
95
+ RecordStore.fromString(`
96
+ => e006010315058000002c80001a6f800000008000000080000000
97
+ <= 9000
98
+ => e006010420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
99
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00009000
100
+ `),
101
+ );
102
+ const canton = new Canton(transport);
103
+ const data = {
104
+ transactions: ["d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd"],
105
+ };
106
+
107
+ // WHEN
108
+ const result = await canton.signTransaction(PATH, data);
109
+
110
+ // THEN
111
+ expect(result).toEqual({
112
+ signature:
113
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
114
+ });
115
+ });
116
+
117
+ it("should sign untyped versioned message with challenge", async () => {
118
+ // GIVEN
119
+ const challenge = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
120
+ const transport = await openTransportReplayer(
121
+ RecordStore.fromString(`
122
+ => e006010335058000002c80001a6f800000008000000080000000${challenge}
123
+ <= 9000
124
+ => e006010420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
125
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a000040b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a009000
126
+ `),
127
+ );
128
+ const canton = new Canton(transport);
129
+ const data = {
130
+ transactions: ["d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd"],
131
+ challenge,
132
+ };
133
+
83
134
  // WHEN
84
- const result = await canton.getAddress("44'/6767'/0'/0'/1'");
135
+ const result = await canton.signTransaction(PATH, data);
85
136
 
86
137
  // THEN
87
- expect(result).toBeDefined();
88
- expect(result.address).toBeDefined();
89
- expect(result.publicKey).toBeDefined();
138
+ expect(result).toEqual({
139
+ signature:
140
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
141
+ applicationSignature:
142
+ "b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
143
+ });
90
144
  });
91
145
 
92
- // should handle user refused address
93
- });
146
+ it("should handle challenge with large transaction chunking", async () => {
147
+ // GIVEN
148
+ const challenge = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
149
+ const largeTransaction =
150
+ "d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd".repeat(10);
151
+ const firstChunk = largeTransaction.substring(0, 510);
152
+ const secondChunk = largeTransaction.substring(510, 640);
94
153
 
95
- describe("signTransaction", () => {
96
- // should sign transaction
154
+ const transport = await openTransportReplayer(
155
+ RecordStore.fromString(`
156
+ => e006010335058000002c80001a6f800000008000000080000000${challenge}
157
+ <= 9000
158
+ => e0060102ff${firstChunk}
159
+ <= 9000
160
+ => e006010441${secondChunk}
161
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a000040b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a009000
162
+ `),
163
+ );
164
+ const canton = new Canton(transport);
165
+ const data = {
166
+ transactions: [largeTransaction],
167
+ challenge,
168
+ };
169
+
170
+ // WHEN
171
+ const result = await canton.signTransaction(PATH, data);
172
+
173
+ // THEN
174
+ expect(result).toEqual({
175
+ signature:
176
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
177
+ applicationSignature:
178
+ "b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
179
+ });
180
+ });
181
+
182
+ it("should sign large untyped versioned message with chunking", async () => {
183
+ // GIVEN
184
+ const largeTransaction =
185
+ "d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd".repeat(10);
186
+
187
+ const firstChunk = largeTransaction.substring(0, 510);
188
+ const secondChunk = largeTransaction.substring(510, 640);
97
189
 
98
- // should handle large transaction payloads
190
+ const transport = await openTransportReplayer(
191
+ RecordStore.fromString(`
192
+ => e006010315058000002c80001a6f800000008000000080000000
193
+ <= 9000
194
+ => e0060102ff${firstChunk}
195
+ <= 9000
196
+ => e006010441${secondChunk}
197
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00009000
198
+ `),
199
+ );
200
+ const canton = new Canton(transport);
201
+ const data = {
202
+ transactions: [largeTransaction],
203
+ };
99
204
 
100
- // should handle empty transaction
205
+ // WHEN
206
+ const result = await canton.signTransaction(PATH, data);
101
207
 
102
- // should request blind signature when required
208
+ // THEN
209
+ expect(result).toEqual({
210
+ signature:
211
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
212
+ });
213
+ });
103
214
 
104
- it("should sign transaction hash", async () => {
215
+ it("should sign prepared transaction", async () => {
105
216
  // GIVEN
106
217
  const transport = await openTransportReplayer(
107
218
  RecordStore.fromString(`
108
- => e006000315058000002c80001a6f800000008000000080000000
219
+ => e006020315058000002c80001a6f800000008000000080000000
220
+ <= 9000
221
+ => e0060202ff${"1234567890abcdef".repeat(31)}1234567890abcd
109
222
  <= 9000
110
- => e006000420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
111
- <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00009000
223
+ => e006020608ef1234567890abcd
224
+ <= 9000
225
+ => e0060202ff${"1234567890abcdef".repeat(31)}1234567890abcd
226
+ <= 9000
227
+ => e006020608ef1234567890abcd
228
+ <= 9000
229
+ => e0060202ff${"1234567890abcdef".repeat(31)}1234567890abcd
230
+ <= 9000
231
+ => e006020608ef1234567890abcd
232
+ <= 9000
233
+ => e0060202ff${"1234567890abcdef".repeat(31)}1234567890abcd
234
+ <= 9000
235
+ => e006020408ef1234567890abcd
236
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a009000
112
237
  `),
113
238
  );
114
239
  const canton = new Canton(transport);
115
- const txHash = "d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd";
240
+
241
+ const testData = "1234567890abcdef".repeat(31) + "1234567890abcdef1234567890abcd";
242
+ const components = {
243
+ damlTransaction: Buffer.from(testData, "hex"),
244
+ nodes: [Buffer.from(testData, "hex")],
245
+ metadata: Buffer.from(testData, "hex"),
246
+ inputContracts: [Buffer.from(testData, "hex")],
247
+ };
116
248
 
117
249
  // WHEN
118
- const result = await canton.signTransaction("44'/6767'/0'/0'/0'", txHash);
250
+ const result = await canton.signTransaction(PATH, components);
119
251
 
120
252
  // THEN
121
- expect(result).toEqual(
122
- "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
123
- );
253
+ expect(result).toEqual({
254
+ signature:
255
+ "40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
256
+ });
124
257
  });
125
258
 
126
259
  it("should handle user refused transaction", async () => {
127
260
  // GIVEN
128
261
  const transport = await openTransportReplayer(
129
262
  RecordStore.fromString(`
130
- => e006010015058000002c80001a6f800000008000000080000000
131
- <= 6985
132
- `),
263
+ => e006010315058000002c80001a6f800000008000000080000000
264
+ <= 6985
265
+ `),
266
+ );
267
+ const canton = new Canton(transport);
268
+ const data = {
269
+ transactions: ["test"],
270
+ };
271
+
272
+ // WHEN & THEN
273
+ await expect(canton.signTransaction(PATH, data)).rejects.toThrow(
274
+ new TransportStatusError(0x6985),
133
275
  );
276
+ });
277
+
278
+ it("should handle invalid transaction data", async () => {
279
+ // GIVEN
280
+ const transport = await openTransportReplayer(new RecordStore());
134
281
  const canton = new Canton(transport);
135
282
 
136
283
  // WHEN & THEN
137
- return expect(canton.signTransaction("44'/6767'/0'/0'/0'", "test")).rejects.toThrow();
284
+ await expect(canton.signTransaction(PATH, null as any)).rejects.toThrow();
138
285
  });
139
286
  });
140
287
 
@@ -144,7 +291,7 @@ describe("Canton", () => {
144
291
  const transport = await openTransportReplayer(
145
292
  RecordStore.fromString(`
146
293
  => e003000000
147
- <= 0202029000
294
+ <= 0101009000
148
295
  `),
149
296
  );
150
297
  const canton = new Canton(transport);
@@ -154,10 +301,116 @@ describe("Canton", () => {
154
301
 
155
302
  // THEN
156
303
  expect(result).toEqual({
157
- version: "2.2.2",
304
+ version: "1.1.0",
305
+ });
306
+ });
307
+ });
308
+
309
+ describe("parseSignatureResponse", () => {
310
+ it("should parse TLV format signature without challenge", async () => {
311
+ // GIVEN
312
+ const transport = await openTransportReplayer(
313
+ RecordStore.fromString(`
314
+ => e006010315058000002c80001a6f800000008000000080000000
315
+ <= 9000
316
+ => e006010420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
317
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a000040b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a009000
318
+ `),
319
+ );
320
+ const canton = new Canton(transport);
321
+ const data = {
322
+ transactions: ["d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd"],
323
+ };
324
+
325
+ // WHEN
326
+ const result = await canton.signTransaction(PATH, data);
327
+
328
+ // THEN
329
+ // The response is a TLV format but without challenge, so it should return a CantonMultiSignature
330
+ expect(result).toEqual({
331
+ signature:
332
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
333
+ applicationSignature: undefined,
334
+ });
335
+ });
336
+
337
+ it("should parse TLV format signature with challenge", async () => {
338
+ // GIVEN
339
+ const challenge = "1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef";
340
+ const transport = await openTransportReplayer(
341
+ RecordStore.fromString(`
342
+ => e006010335058000002c80001a6f800000008000000080000000${challenge}
343
+ <= 9000
344
+ => e006010420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
345
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a000040b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a009000
346
+ `),
347
+ );
348
+ const canton = new Canton(transport);
349
+ const data = {
350
+ transactions: ["d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd"],
351
+ challenge,
352
+ };
353
+
354
+ // WHEN
355
+ const result = await canton.signTransaction(PATH, data);
356
+
357
+ // THEN
358
+ expect(result).toEqual({
359
+ signature:
360
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
361
+ applicationSignature:
362
+ "b65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
363
+ });
364
+ });
365
+
366
+ it("should parse single Ed25519 signature", async () => {
367
+ // GIVEN
368
+ const transport = await openTransportReplayer(
369
+ RecordStore.fromString(`
370
+ => e006010315058000002c80001a6f800000008000000080000000
371
+ <= 9000
372
+ => e006010420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
373
+ <= a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a009000
374
+ `),
375
+ );
376
+ const canton = new Canton(transport);
377
+ const data = {
378
+ transactions: ["d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd"],
379
+ };
380
+
381
+ // WHEN
382
+ const result = await canton.signTransaction(PATH, data);
383
+
384
+ // THEN
385
+ expect(result).toEqual({
386
+ signature:
387
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
158
388
  });
159
389
  });
160
390
 
161
- // should handle configuration error
391
+ it("should parse Canton-framed signature", async () => {
392
+ // GIVEN
393
+ const transport = await openTransportReplayer(
394
+ RecordStore.fromString(`
395
+ => e006010315058000002c80001a6f800000008000000080000000
396
+ <= 9000
397
+ => e006010420d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd
398
+ <= 40a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00009000
399
+ `),
400
+ );
401
+ const canton = new Canton(transport);
402
+ const data = {
403
+ transactions: ["d1e98829444207b0e170346b2e80b58a2ffc602b01e190fb742016d407c84efd"],
404
+ };
405
+
406
+ // WHEN
407
+ const result = await canton.signTransaction(PATH, data);
408
+
409
+ // THEN
410
+ expect(result).toEqual({
411
+ signature:
412
+ "a65f53c3657bc04efefb67a425ba093a5cb5391d18142f148bb2c48daacf316114cff920a58d5996ca828c7ce265f537f1d7fca8fa82c3c73bd944a96e701a00",
413
+ });
414
+ });
162
415
  });
163
416
  });