@docknetwork/wallet-sdk-wasm 1.5.14 → 1.7.0

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.
Files changed (91) hide show
  1. package/generate-docs.js +49 -0
  2. package/jsdoc.conf.json +29 -6
  3. package/lib/index.js +8 -1
  4. package/lib/index.mjs +8 -1
  5. package/lib/rpc-server.js +10 -1
  6. package/lib/rpc-server.mjs +10 -1
  7. package/lib/services/blockchain/cached-did-resolver.js +113 -0
  8. package/lib/services/blockchain/cached-did-resolver.mjs +109 -0
  9. package/lib/services/blockchain/index.js +11 -0
  10. package/lib/services/blockchain/index.mjs +11 -0
  11. package/lib/services/blockchain/service-rpc.js +12 -0
  12. package/lib/services/blockchain/service-rpc.mjs +12 -0
  13. package/lib/services/blockchain/service.js +140 -11
  14. package/lib/services/blockchain/service.mjs +140 -11
  15. package/lib/services/credential/bbs-revocation.js +11 -0
  16. package/lib/services/credential/bbs-revocation.mjs +11 -0
  17. package/lib/services/credential/config.js +4 -1
  18. package/lib/services/credential/config.mjs +4 -1
  19. package/lib/services/credential/index.js +14 -0
  20. package/lib/services/credential/index.mjs +14 -0
  21. package/lib/services/credential/sd-jwt.js +214 -0
  22. package/lib/services/credential/sd-jwt.mjs +200 -0
  23. package/lib/services/credential/service-rpc.js +9 -0
  24. package/lib/services/credential/service-rpc.mjs +9 -0
  25. package/lib/services/credential/service.js +324 -7
  26. package/lib/services/credential/service.mjs +324 -7
  27. package/lib/services/edv/service.js +145 -1
  28. package/lib/services/edv/service.mjs +145 -1
  29. package/lib/services/index.js +13 -0
  30. package/lib/services/index.mjs +13 -0
  31. package/lib/services/relay-service/service.js +124 -1
  32. package/lib/services/relay-service/service.mjs +124 -1
  33. package/lib/services/rpc-service-client.js +0 -3
  34. package/lib/services/rpc-service-client.mjs +0 -3
  35. package/lib/services/storage/index.js +19 -2
  36. package/lib/services/storage/index.mjs +24 -1
  37. package/lib/services/storage/service-rpc.js +7 -3
  38. package/lib/services/storage/service-rpc.mjs +7 -3
  39. package/lib/services/storage/service.js +4 -0
  40. package/lib/services/storage/service.mjs +4 -0
  41. package/lib/setup-nodejs.js +8 -1
  42. package/lib/setup-nodejs.mjs +8 -1
  43. package/lib/setup-tests.js +8 -1
  44. package/lib/setup-tests.mjs +8 -1
  45. package/lib/src/services/blockchain/cached-did-resolver.d.ts +28 -0
  46. package/lib/src/services/blockchain/cached-did-resolver.d.ts.map +1 -0
  47. package/lib/src/services/blockchain/cached-did-resolver.test.d.ts +2 -0
  48. package/lib/src/services/blockchain/cached-did-resolver.test.d.ts.map +1 -0
  49. package/lib/src/services/blockchain/service.d.ts +114 -17
  50. package/lib/src/services/blockchain/service.d.ts.map +1 -1
  51. package/lib/src/services/credential/config.d.ts.map +1 -1
  52. package/lib/src/services/credential/index.d.ts +3 -0
  53. package/lib/src/services/credential/index.d.ts.map +1 -1
  54. package/lib/src/services/credential/sd-jwt.test.d.ts +2 -0
  55. package/lib/src/services/credential/sd-jwt.test.d.ts.map +1 -0
  56. package/lib/src/services/credential/service.d.ts +274 -4
  57. package/lib/src/services/credential/service.d.ts.map +1 -1
  58. package/lib/src/services/edv/service.d.ts +151 -1
  59. package/lib/src/services/edv/service.d.ts.map +1 -1
  60. package/lib/src/services/relay-service/service.d.ts +129 -1
  61. package/lib/src/services/relay-service/service.d.ts.map +1 -1
  62. package/lib/src/services/rpc-service-client.d.ts +2 -2
  63. package/lib/src/services/rpc-service-client.d.ts.map +1 -1
  64. package/lib/src/services/storage/index.d.ts +1 -1
  65. package/lib/src/services/storage/index.d.ts.map +1 -1
  66. package/lib/src/services/storage/service-rpc.d.ts +9 -0
  67. package/lib/src/services/storage/service-rpc.d.ts.map +1 -0
  68. package/lib/src/services/storage/service.d.ts +1 -0
  69. package/lib/src/services/storage/service.d.ts.map +1 -1
  70. package/lib/src/services/util-crypto/service.d.ts +1 -1
  71. package/lib/tsconfig.tsbuildinfo +1 -1
  72. package/lib/wallet/rpc-storage-interface.js +13 -3
  73. package/lib/wallet/rpc-storage-interface.mjs +11 -1
  74. package/lib/wallet/rpc-storage-wallet.js +10 -0
  75. package/lib/wallet/rpc-storage-wallet.mjs +10 -0
  76. package/package.json +13 -8
  77. package/src/services/blockchain/cached-did-resolver.test.ts +288 -0
  78. package/src/services/blockchain/cached-did-resolver.ts +126 -0
  79. package/src/services/blockchain/service-rpc.js +12 -0
  80. package/src/services/blockchain/service.ts +142 -11
  81. package/src/services/credential/config.ts +7 -1
  82. package/src/services/credential/sd-jwt.test.ts +718 -0
  83. package/src/services/credential/sd-jwt.ts +231 -0
  84. package/src/services/credential/service-rpc.js +9 -0
  85. package/src/services/credential/service.ts +328 -7
  86. package/src/services/edv/service.ts +153 -1
  87. package/src/services/relay-service/service.ts +130 -1
  88. package/src/services/rpc-service-client.js +0 -3
  89. package/src/services/storage/index.js +15 -1
  90. package/src/services/storage/service-rpc.js +7 -3
  91. package/src/services/storage/service.ts +5 -0
@@ -0,0 +1,718 @@
1
+ import {
2
+ isSDJWTCredential,
3
+ sdJwtToW3C,
4
+ decodeSDJWTToW3C,
5
+ credentialToW3C,
6
+ verifySDJWT,
7
+ decodeSDJWT,
8
+ createSDJWTPresentation,
9
+ } from './sd-jwt';
10
+
11
+ // Test SD-JWT credential provided by the user
12
+ const TEST_SD_JWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJraWQiOiJkaWQ6Y2hlcWQ6dGVzdG5ldDpjMDg5MGYxYy1jN2JiLTRlYTYtYmU3YS04YzMxNDA0NzQzYjcja2V5cy0xIiwiYWxnIjoiRWREU0EifQ.eyJpYXQiOjE3NTk0MTQzOTQsImlzcyI6ImRpZDpjaGVxZDp0ZXN0bmV0OmMwODkwZjFjLWM3YmItNGVhNi1iZTdhLThjMzE0MDQ3NDNiNyNrZXlzLTEiLCJ2Y3QiOiJJbnRlcm5hbFRlc3RpbmciLCJfc2QiOlsiM3JVUGt1Mk5XckFFeTV3ZE9uVms5TkJBa0haMWE4RDB6Y2liMFNQdmthWSIsIkhjNHAxMnZTTWNMQ0piNVRYVlFrdFFMR2xjM0J3SDNWN2ltakV5ZDdvdzAiLCJNZWNNZEd6NjAxY3kwTTdvanRtSjR1LUI5LTJxSXAya1RvbFpDUm1GZ1pFIiwiZmo4WHdBb0lERmRsWmpEa0NTVzVpeXBPYUZBcVplTWZDRncwTWd3cHhOQSJdLCJfc2RfYWxnIjoic2hhLTI1NiJ9.FcYvNrldceL5BTNmoIaS4Mub8a5NcbiseUeSmmvUpOW8SUom-bchV5AEefrH1VMECbdc2whhk2sW4_jmZo7_Dw~WyI1YTRkZWY1MTAzYjRiYjc0IiwiaWQiLCJkaWQ6a2V5Ono2TWt1OVI4emRBOExENmhjRlhrbjQ3akxuZmNLWk5HbXdhVHJEbmFDQmtTYjhVbiJd~WyJiZDUyMjQ5ZjAyNjk3NWRkIiwiZGF0ZSIsIjIwMjUtMTAtMDkiXQ~WyJlMjcxNzExNzVkODdlMWE1IiwibmFtZSIsIm1heWNvbiJd~WyIwZDFiOTBlNTlhNmMyNDNiIiwibnVtYmVyIiwxMjNd~';
13
+
14
+ // Regular JWT (not SD-JWT) for testing
15
+ const REGULAR_JWT = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
16
+
17
+ // Regular JWT with dc+sd-jwt type
18
+ const DC_SD_JWT = 'eyJ0eXAiOiJkYytzZC1qd3QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c';
19
+
20
+ // Sample W3C credential for testing
21
+ const W3C_CREDENTIAL = {
22
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
23
+ type: ['VerifiableCredential', 'TestCredential'],
24
+ issuer: 'did:example:issuer',
25
+ credentialSubject: {
26
+ id: 'did:example:subject',
27
+ name: 'Test Subject',
28
+ },
29
+ };
30
+
31
+ describe('SD-JWT Service', () => {
32
+ describe('isSDJWTCredential', () => {
33
+ it('should return true for vc+sd-jwt credential', () => {
34
+ const result = isSDJWTCredential(TEST_SD_JWT);
35
+ expect(result).toBe(true);
36
+ });
37
+
38
+ it('should return true for dc+sd-jwt credential', () => {
39
+ const result = isSDJWTCredential(DC_SD_JWT);
40
+ expect(result).toBe(true);
41
+ });
42
+
43
+ it('should return false for regular JWT', () => {
44
+ const result = isSDJWTCredential(REGULAR_JWT);
45
+ expect(result).toBe(false);
46
+ });
47
+
48
+ it('should handle JWT without typ field', () => {
49
+ const jwtWithoutTyp = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U';
50
+ const result = isSDJWTCredential(jwtWithoutTyp);
51
+ expect(result).toBe(false);
52
+ });
53
+ });
54
+
55
+ describe('sdJwtToW3C', () => {
56
+ it('should convert decoded SD-JWT to W3C format with all fields', () => {
57
+ const decodedSDJWT = {
58
+ jwt: {
59
+ header: {
60
+ typ: 'vc+sd-jwt',
61
+ alg: 'EdDSA',
62
+ },
63
+ payload: {
64
+ iss: 'did:example:issuer',
65
+ vct: 'TestCredential',
66
+ iat: 1609459200, // 2021-01-01T00:00:00.000Z
67
+ exp: 1640995200, // 2022-01-01T00:00:00.000Z
68
+ jti: 'credential-id-123',
69
+ },
70
+ },
71
+ disclosures: [
72
+ { key: 'id', value: 'did:example:subject' },
73
+ { key: 'name', value: 'John Doe' },
74
+ { key: 'age', value: 30 },
75
+ ],
76
+ };
77
+
78
+ const result = sdJwtToW3C(decodedSDJWT);
79
+
80
+ expect(result).toEqual({
81
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
82
+ type: ['VerifiableCredential', 'TestCredential'],
83
+ issuer: 'did:example:issuer',
84
+ credentialSubject: {
85
+ id: 'did:example:subject',
86
+ name: 'John Doe',
87
+ age: 30,
88
+ },
89
+ issuanceDate: '2021-01-01T00:00:00.000Z',
90
+ expirationDate: '2022-01-01T00:00:00.000Z',
91
+ id: 'credential-id-123',
92
+ _sd_jwt: {
93
+ encoded: undefined,
94
+ },
95
+ });
96
+ });
97
+
98
+ it('should store raw encoded SD-JWT when provided', () => {
99
+ const decodedSDJWT = {
100
+ jwt: {
101
+ header: {
102
+ typ: 'vc+sd-jwt',
103
+ alg: 'EdDSA',
104
+ },
105
+ payload: {
106
+ iss: 'did:example:issuer',
107
+ vct: 'TestCredential',
108
+ },
109
+ },
110
+ disclosures: [
111
+ { key: 'name', value: 'Jane Doe' },
112
+ ],
113
+ };
114
+
115
+ const encodedSDJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIifQ.signature~disclosure1~disclosure2~';
116
+ const result = sdJwtToW3C(decodedSDJWT, encodedSDJWT);
117
+
118
+ expect(result._sd_jwt).toBeDefined();
119
+ expect(result._sd_jwt.encoded).toBe(encodedSDJWT);
120
+ });
121
+
122
+ it('should handle minimal SD-JWT without optional fields', () => {
123
+ const decodedSDJWT = {
124
+ jwt: {
125
+ header: {
126
+ typ: 'vc+sd-jwt',
127
+ alg: 'EdDSA',
128
+ },
129
+ payload: {
130
+ iss: 'did:example:issuer',
131
+ vct: 'TestCredential',
132
+ },
133
+ },
134
+ disclosures: [
135
+ { key: 'name', value: 'Jane Doe' },
136
+ ],
137
+ };
138
+
139
+ const result = sdJwtToW3C(decodedSDJWT);
140
+
141
+ expect(result).toEqual({
142
+ '@context': ['https://www.w3.org/2018/credentials/v1'],
143
+ type: ['VerifiableCredential', 'TestCredential'],
144
+ issuer: 'did:example:issuer',
145
+ credentialSubject: {
146
+ name: 'Jane Doe',
147
+ },
148
+ _sd_jwt: {
149
+ encoded: undefined,
150
+ },
151
+ });
152
+ });
153
+
154
+ it('should handle empty disclosures array', () => {
155
+ const decodedSDJWT = {
156
+ jwt: {
157
+ header: {
158
+ typ: 'vc+sd-jwt',
159
+ alg: 'EdDSA',
160
+ },
161
+ payload: {
162
+ iss: 'did:example:issuer',
163
+ vct: 'TestCredential',
164
+ },
165
+ },
166
+ disclosures: [],
167
+ };
168
+
169
+ const result = sdJwtToW3C(decodedSDJWT);
170
+
171
+ expect(result.credentialSubject).toEqual({});
172
+ });
173
+
174
+ it('should use issuer field from payload if iss is not present', () => {
175
+ const decodedSDJWT = {
176
+ jwt: {
177
+ header: {
178
+ typ: 'vc+sd-jwt',
179
+ alg: 'EdDSA',
180
+ },
181
+ payload: {
182
+ issuer: 'did:example:issuer-from-issuer-field',
183
+ vct: 'TestCredential',
184
+ },
185
+ },
186
+ disclosures: [],
187
+ };
188
+
189
+ const result = sdJwtToW3C(decodedSDJWT);
190
+
191
+ expect(result.issuer).toBe('did:example:issuer-from-issuer-field');
192
+ });
193
+
194
+ it('should handle disclosures without key (malformed)', () => {
195
+ const decodedSDJWT = {
196
+ jwt: {
197
+ header: {
198
+ typ: 'vc+sd-jwt',
199
+ alg: 'EdDSA',
200
+ },
201
+ payload: {
202
+ iss: 'did:example:issuer',
203
+ vct: 'TestCredential',
204
+ },
205
+ },
206
+ disclosures: [
207
+ { key: 'name', value: 'Valid' },
208
+ { value: 'Invalid' }, // Missing key
209
+ null, // Null disclosure
210
+ ],
211
+ };
212
+
213
+ const result = sdJwtToW3C(decodedSDJWT);
214
+
215
+ expect(result.credentialSubject).toEqual({
216
+ name: 'Valid',
217
+ });
218
+ });
219
+
220
+ it('should use UnknownCredential when vct is missing', () => {
221
+ const decodedSDJWT = {
222
+ jwt: {
223
+ header: {
224
+ typ: 'vc+sd-jwt',
225
+ alg: 'EdDSA',
226
+ },
227
+ payload: {
228
+ iss: 'did:example:issuer',
229
+ },
230
+ },
231
+ disclosures: [],
232
+ };
233
+
234
+ const result = sdJwtToW3C(decodedSDJWT);
235
+
236
+ expect(result.type).toEqual(['VerifiableCredential', 'UnknownCredential']);
237
+ });
238
+ });
239
+
240
+ describe('decodeSDJWTToW3C', () => {
241
+ it('should decode the provided test SD-JWT and convert to W3C format', async () => {
242
+ const result = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
243
+
244
+ // Verify the structure
245
+ expect(result).toHaveProperty('@context');
246
+ expect(result).toHaveProperty('type');
247
+ expect(result).toHaveProperty('issuer');
248
+ expect(result).toHaveProperty('credentialSubject');
249
+ expect(result).toHaveProperty('issuanceDate');
250
+
251
+ // Verify specific values from the test SD-JWT
252
+ expect(result.type).toEqual(['VerifiableCredential', 'InternalTesting']);
253
+ expect(result.issuer).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
254
+
255
+ // Verify credential subject has disclosed claims
256
+ expect(result.credentialSubject).toHaveProperty('id');
257
+ expect(result.credentialSubject).toHaveProperty('name');
258
+ expect(result.credentialSubject).toHaveProperty('date');
259
+ expect(result.credentialSubject).toHaveProperty('number');
260
+
261
+ // Verify disclosed values
262
+ expect(result.credentialSubject.id).toBe('did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un');
263
+ expect(result.credentialSubject.name).toBe('maycon');
264
+ expect(result.credentialSubject.date).toBe('2025-10-09');
265
+ expect(result.credentialSubject.number).toBe(123);
266
+
267
+ // Verify issuance date
268
+ expect(result.issuanceDate).toBe(new Date(1759414394 * 1000).toISOString());
269
+ });
270
+
271
+ it('should store raw encoded SD-JWT and decoded structure in metadata', async () => {
272
+ const result = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
273
+
274
+ // Verify _sd_jwt metadata is present
275
+ expect(result).toHaveProperty('_sd_jwt');
276
+ expect(result._sd_jwt).toHaveProperty('encoded');
277
+
278
+ // Verify raw SD-JWT string is stored
279
+ expect(result._sd_jwt.encoded).toBe(TEST_SD_JWT);
280
+ });
281
+
282
+ it('should handle SD-JWT with minimal disclosures', async () => {
283
+ // This test would need a valid minimal SD-JWT
284
+ // For now, we'll just test that the function is async and can be called
285
+ await expect(decodeSDJWTToW3C(TEST_SD_JWT)).resolves.toBeDefined();
286
+ });
287
+ });
288
+
289
+ describe('credentialToW3C', () => {
290
+ it('should return W3C credential as-is when passed an object with type', async () => {
291
+ const result = await credentialToW3C(W3C_CREDENTIAL);
292
+ expect(result).toEqual(W3C_CREDENTIAL);
293
+ });
294
+
295
+ it('should decode SD-JWT string to W3C format', async () => {
296
+ const result = await credentialToW3C(TEST_SD_JWT);
297
+
298
+ expect(result).toHaveProperty('@context');
299
+ expect(result).toHaveProperty('type');
300
+ expect(result).toHaveProperty('issuer');
301
+ expect(result).toHaveProperty('credentialSubject');
302
+ expect(result.type).toEqual(['VerifiableCredential', 'InternalTesting']);
303
+ });
304
+
305
+ it('should parse JSON string containing W3C credential', async () => {
306
+ const jsonString = JSON.stringify(W3C_CREDENTIAL);
307
+ const result = await credentialToW3C(jsonString);
308
+ expect(result).toEqual(W3C_CREDENTIAL);
309
+ });
310
+
311
+ it('should throw error for unsupported credential format', async () => {
312
+ const invalidCredential = 'not-a-valid-jwt-or-credential';
313
+
314
+ await expect(credentialToW3C(invalidCredential)).rejects.toThrow(
315
+ 'Unable to convert credential to W3C format'
316
+ );
317
+ });
318
+
319
+ it('should throw error for empty string', async () => {
320
+ await expect(credentialToW3C('')).rejects.toThrow();
321
+ });
322
+
323
+ it('should handle object without type field', async () => {
324
+ const invalidObject = {
325
+ someField: 'someValue',
326
+ };
327
+
328
+ await expect(credentialToW3C(invalidObject)).rejects.toThrow(
329
+ 'Unable to convert credential to W3C format'
330
+ );
331
+ });
332
+
333
+ it('should handle regular JWT (not SD-JWT)', async () => {
334
+ await expect(credentialToW3C(REGULAR_JWT)).rejects.toThrow(
335
+ 'Unable to convert credential to W3C format'
336
+ );
337
+ });
338
+ });
339
+
340
+ describe('Integration tests', () => {
341
+ it('should properly decode and convert complete SD-JWT flow', async () => {
342
+ // Test the complete flow: check if SD-JWT -> decode -> convert to W3C
343
+ const isSDJWT = isSDJWTCredential(TEST_SD_JWT);
344
+ expect(isSDJWT).toBe(true);
345
+
346
+ const w3cCredential = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
347
+
348
+ // Verify complete structure
349
+ expect(w3cCredential).toMatchObject({
350
+ '@context': expect.arrayContaining(['https://www.w3.org/2018/credentials/v1']),
351
+ type: expect.arrayContaining(['VerifiableCredential', 'InternalTesting']),
352
+ issuer: 'did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1',
353
+ credentialSubject: {
354
+ id: 'did:key:z6Mku9R8zdA8LD6hcFXkn47jLnfcKZNGmwaTrDnaCBkSb8Un',
355
+ name: 'maycon',
356
+ date: '2025-10-09',
357
+ number: 123,
358
+ },
359
+ });
360
+
361
+ // Verify SD-JWT metadata is stored for unwrapping
362
+ expect(w3cCredential._sd_jwt).toBeDefined();
363
+ expect(w3cCredential._sd_jwt.encoded).toBe(TEST_SD_JWT);
364
+ });
365
+
366
+ it('should handle credentialToW3C with the test SD-JWT', async () => {
367
+ const result = await credentialToW3C(TEST_SD_JWT) as any;
368
+
369
+ expect(result.credentialSubject.name).toBe('maycon');
370
+ expect(result.credentialSubject.number).toBe(123);
371
+ expect(result.issuer).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
372
+
373
+ // Verify SD-JWT metadata for presentation unwrapping
374
+ expect(result._sd_jwt).toBeDefined();
375
+ expect(result._sd_jwt.encoded).toBe(TEST_SD_JWT);
376
+ });
377
+ });
378
+
379
+ describe('decodeSDJWT', () => {
380
+ it('should decode SD-JWT into structured format', async () => {
381
+ const decoded = await decodeSDJWT(TEST_SD_JWT);
382
+
383
+ expect(decoded).toHaveProperty('jwt');
384
+ expect(decoded).toHaveProperty('disclosures');
385
+ expect(decoded.jwt).toHaveProperty('header');
386
+ expect(decoded.jwt).toHaveProperty('payload');
387
+ expect(Array.isArray(decoded.disclosures)).toBe(true);
388
+ });
389
+
390
+ it('should decode header and payload correctly', async () => {
391
+ const decoded = await decodeSDJWT(TEST_SD_JWT);
392
+
393
+ expect(decoded.jwt.header.typ).toBe('vc+sd-jwt');
394
+ expect(decoded.jwt.header.alg).toBe('EdDSA');
395
+ expect(decoded.jwt.payload.iss).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
396
+ expect(decoded.jwt.payload.vct).toBe('InternalTesting');
397
+ });
398
+
399
+ it('should throw error for invalid SD-JWT', async () => {
400
+ await expect(decodeSDJWT('invalid-jwt-string')).rejects.toThrow();
401
+ });
402
+ });
403
+
404
+ describe('verifySDJWT', () => {
405
+ it('should verify valid SD-JWT successfully', async () => {
406
+ const result = await verifySDJWT(TEST_SD_JWT);
407
+
408
+ expect(result).toHaveProperty('verified');
409
+ expect(result.verified).toBe(true);
410
+ expect(result.error).toBeUndefined();
411
+ });
412
+
413
+ it('should fail verification for expired SD-JWT', async () => {
414
+ // Create an expired SD-JWT (exp in the past)
415
+ const expiredJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJ2Y3QiOiJUZXN0Q3JlZGVudGlhbCIsImV4cCI6MTYwOTQ1OTIwMCwiaWF0IjoxNjA5NDU5MjAwfQ.signature~';
416
+
417
+ const result = await verifySDJWT(expiredJWT);
418
+
419
+ expect(result.verified).toBe(false);
420
+ expect(result.error).toContain('expired');
421
+ });
422
+
423
+ it('should fail verification for not-yet-valid SD-JWT', async () => {
424
+ // Create an SD-JWT with nbf in the future
425
+ const futureJWT = 'eyJ0eXAiOiJ2YytzZC1qd3QiLCJhbGciOiJFZERTQSJ9.eyJpc3MiOiJkaWQ6ZXhhbXBsZTppc3N1ZXIiLCJ2Y3QiOiJUZXN0Q3JlZGVudGlhbCIsIm5iZiI6OTk5OTk5OTk5OSwiaWF0IjoxNjA5NDU5MjAwfQ.signature~';
426
+
427
+ const result = await verifySDJWT(futureJWT);
428
+
429
+ expect(result.verified).toBe(false);
430
+ expect(result.error).toContain('not yet valid');
431
+ });
432
+
433
+ it('should fail verification for invalid SD-JWT format', async () => {
434
+ const result = await verifySDJWT('invalid-jwt-string');
435
+
436
+ expect(result.verified).toBe(false);
437
+ expect(result.error).toBeDefined();
438
+ });
439
+
440
+ it('should verify SD-JWT without expiration date', async () => {
441
+ // The TEST_SD_JWT doesn't have an expiration date, so it should verify
442
+ const result = await verifySDJWT(TEST_SD_JWT);
443
+
444
+ expect(result.verified).toBe(true);
445
+ });
446
+
447
+ it('should return error message on verification failure', async () => {
448
+ const result = await verifySDJWT('');
449
+
450
+ expect(result.verified).toBe(false);
451
+ expect(result).toHaveProperty('error');
452
+ expect(typeof result.error).toBe('string');
453
+ });
454
+ });
455
+
456
+ describe('createSDJWTPresentation', () => {
457
+ it('should create presentation with selective disclosure of specific attributes', async () => {
458
+ // Test selective disclosure - reveal only name and date, hide id and number
459
+ const presentation = await createSDJWTPresentation({
460
+ attributesToReveal: ['name', 'date'],
461
+ credential: TEST_SD_JWT,
462
+ });
463
+
464
+ // Presentation should be a string
465
+ expect(typeof presentation).toBe('string');
466
+
467
+ // Should be a valid JWT format with disclosures
468
+ expect(presentation).toContain('~');
469
+
470
+ // Decode the presentation to verify only requested attributes are included
471
+ const decoded = await decodeSDJWT(presentation);
472
+ const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
473
+
474
+ // Should include requested attributes
475
+ expect(disclosureKeys).toContain('name');
476
+ expect(disclosureKeys).toContain('date');
477
+
478
+ // Should NOT include unrequested attributes
479
+ expect(disclosureKeys).not.toContain('number');
480
+ // Note: 'id' might be included by default in some implementations
481
+ });
482
+
483
+ it('should create presentation revealing only one attribute', async () => {
484
+ const presentation = await createSDJWTPresentation({
485
+ attributesToReveal: ['name'],
486
+ credential: TEST_SD_JWT,
487
+ });
488
+
489
+ expect(typeof presentation).toBe('string');
490
+
491
+ const decoded = await decodeSDJWT(presentation);
492
+ const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
493
+
494
+ // Should only include the requested attribute
495
+ expect(disclosureKeys).toContain('name');
496
+ expect(disclosureKeys.filter((k: string) => k !== 'name').length).toBeLessThan(disclosureKeys.length);
497
+ });
498
+
499
+ it('should create presentation revealing all attributes', async () => {
500
+ const presentation = await createSDJWTPresentation({
501
+ attributesToReveal: ['id', 'name', 'date', 'number'],
502
+ credential: TEST_SD_JWT,
503
+ });
504
+
505
+ expect(typeof presentation).toBe('string');
506
+
507
+ const decoded = await decodeSDJWT(presentation);
508
+ const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
509
+
510
+ // Should include all requested attributes
511
+ expect(disclosureKeys).toContain('id');
512
+ expect(disclosureKeys).toContain('name');
513
+ expect(disclosureKeys).toContain('date');
514
+ expect(disclosureKeys).toContain('number');
515
+ });
516
+
517
+ it('should create presentation with empty attributes array (minimal disclosure)', async () => {
518
+ const presentation = await createSDJWTPresentation({
519
+ attributesToReveal: [],
520
+ credential: TEST_SD_JWT,
521
+ });
522
+
523
+ expect(typeof presentation).toBe('string');
524
+ expect(presentation).toContain('~');
525
+
526
+ // Should still be valid JWT format
527
+ const decoded = await decodeSDJWT(presentation);
528
+ expect(decoded).toHaveProperty('jwt');
529
+ expect(decoded).toHaveProperty('disclosures');
530
+
531
+ // Should have minimal or no disclosures
532
+ expect(Array.isArray(decoded.disclosures)).toBe(true);
533
+ });
534
+
535
+ it('should maintain credential integrity in presentation', async () => {
536
+ const presentation = await createSDJWTPresentation({
537
+ attributesToReveal: ['name', 'date'],
538
+ credential: TEST_SD_JWT,
539
+ });
540
+
541
+ const decoded = await decodeSDJWT(presentation);
542
+
543
+ // JWT payload should maintain core claims
544
+ expect(decoded.jwt.payload).toHaveProperty('iss');
545
+ expect(decoded.jwt.payload).toHaveProperty('vct');
546
+ expect(decoded.jwt.payload.vct).toBe('InternalTesting');
547
+ expect(decoded.jwt.payload.iss).toBe('did:cheqd:testnet:c0890f1c-c7bb-4ea6-be7a-8c31404743b7#keys-1');
548
+ });
549
+
550
+ it('should convert presentation to W3C format correctly', async () => {
551
+ const presentation = await createSDJWTPresentation({
552
+ attributesToReveal: ['name', 'number'],
553
+ credential: TEST_SD_JWT,
554
+ });
555
+
556
+ // Convert presentation to W3C format
557
+ const w3cPresentation = await decodeSDJWTToW3C(presentation) as any;
558
+
559
+ expect(w3cPresentation).toHaveProperty('credentialSubject');
560
+ expect(w3cPresentation.credentialSubject).toHaveProperty('name');
561
+ expect(w3cPresentation.credentialSubject).toHaveProperty('number');
562
+
563
+ // Should have the revealed values
564
+ expect(w3cPresentation.credentialSubject.name).toBe('maycon');
565
+ expect(w3cPresentation.credentialSubject.number).toBe(123);
566
+ });
567
+
568
+ it('should handle non-existent attribute gracefully', async () => {
569
+ // Request an attribute that doesn't exist in the credential
570
+ const presentation = await createSDJWTPresentation({
571
+ attributesToReveal: ['name', 'nonExistentAttribute'],
572
+ credential: TEST_SD_JWT,
573
+ });
574
+
575
+ expect(typeof presentation).toBe('string');
576
+
577
+ const decoded = await decodeSDJWT(presentation);
578
+ const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
579
+
580
+ // Should include existing attributes
581
+ expect(disclosureKeys).toContain('name');
582
+ // Non-existent attribute should not break the process
583
+ expect(presentation).toBeTruthy();
584
+ });
585
+
586
+ it('should fail with invalid credential format', async () => {
587
+ await expect(
588
+ createSDJWTPresentation({
589
+ attributesToReveal: ['name'],
590
+ credential: 'invalid-jwt-format',
591
+ })
592
+ ).rejects.toThrow();
593
+ });
594
+
595
+ it('should fail with empty credential', async () => {
596
+ await expect(
597
+ createSDJWTPresentation({
598
+ attributesToReveal: ['name'],
599
+ credential: '',
600
+ })
601
+ ).rejects.toThrow();
602
+ });
603
+
604
+ it('should preserve presentation format for verification flow', async () => {
605
+ const presentation = await createSDJWTPresentation({
606
+ attributesToReveal: ['name', 'date'],
607
+ credential: TEST_SD_JWT,
608
+ });
609
+
610
+ // The presentation should be verifiable
611
+ const verificationResult = await verifySDJWT(presentation);
612
+ expect(verificationResult.verified).toBe(true);
613
+ });
614
+ });
615
+
616
+ describe('Presentation Integration Tests', () => {
617
+ it('should complete full flow: store credential -> create presentation -> verify', async () => {
618
+ // Step 1: Convert SD-JWT to W3C format (as done in addCredential)
619
+ const w3cCredential = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
620
+
621
+ // Verify metadata is stored
622
+ expect(w3cCredential._sd_jwt).toBeDefined();
623
+ expect(w3cCredential._sd_jwt.encoded).toBe(TEST_SD_JWT);
624
+
625
+ // Step 2: Create presentation from stored credential (as done in verification-controller)
626
+ const presentation = await createSDJWTPresentation({
627
+ attributesToReveal: ['name', 'date'],
628
+ credential: w3cCredential._sd_jwt.encoded,
629
+ });
630
+
631
+ expect(typeof presentation).toBe('string');
632
+
633
+ // Step 3: Verify presentation
634
+ const verificationResult = await verifySDJWT(presentation);
635
+ expect(verificationResult.verified).toBe(true);
636
+
637
+ // Step 4: Decode presentation to check disclosed claims
638
+ const decoded = await decodeSDJWT(presentation);
639
+ const disclosureKeys = decoded.disclosures.map((d: any) => d.key);
640
+
641
+ expect(disclosureKeys).toContain('name');
642
+ expect(disclosureKeys).toContain('date');
643
+ });
644
+
645
+ it('should support multiple presentation creations from same credential', async () => {
646
+ // Create first presentation with some attributes
647
+ const presentation1 = await createSDJWTPresentation({
648
+ attributesToReveal: ['name'],
649
+ credential: TEST_SD_JWT,
650
+ });
651
+
652
+ // Create second presentation with different attributes
653
+ const presentation2 = await createSDJWTPresentation({
654
+ attributesToReveal: ['date', 'number'],
655
+ credential: TEST_SD_JWT,
656
+ });
657
+
658
+ // Both should be valid
659
+ expect(typeof presentation1).toBe('string');
660
+ expect(typeof presentation2).toBe('string');
661
+
662
+ // They should be different (different disclosures)
663
+ expect(presentation1).not.toBe(presentation2);
664
+
665
+ // Both should verify
666
+ const result1 = await verifySDJWT(presentation1);
667
+ const result2 = await verifySDJWT(presentation2);
668
+
669
+ expect(result1.verified).toBe(true);
670
+ expect(result2.verified).toBe(true);
671
+
672
+ // Check different attributes are disclosed
673
+ const decoded1 = await decodeSDJWT(presentation1);
674
+ const decoded2 = await decodeSDJWT(presentation2);
675
+
676
+ const keys1 = decoded1.disclosures.map((d: any) => d.key);
677
+ const keys2 = decoded2.disclosures.map((d: any) => d.key);
678
+
679
+ expect(keys1).toContain('name');
680
+ expect(keys2).toContain('date');
681
+ expect(keys2).toContain('number');
682
+ });
683
+
684
+ it('should match verification-controller usage pattern', async () => {
685
+ // Simulate the exact pattern used in verification-controller.ts
686
+
687
+ // Credential is stored in wallet as W3C with _sd_jwt metadata
688
+ const storedCredential = await decodeSDJWTToW3C(TEST_SD_JWT) as any;
689
+
690
+ // During presentation, extract encoded SD-JWT and create selective disclosure
691
+ const credentialSelection = {
692
+ credential: storedCredential,
693
+ attributesToReveal: ['name', 'date'],
694
+ };
695
+
696
+ // This is the exact pattern from verification-controller.ts:172-179
697
+ if (credentialSelection.credential._sd_jwt) {
698
+ const derivedCredential = await createSDJWTPresentation({
699
+ attributesToReveal: credentialSelection.attributesToReveal,
700
+ credential: credentialSelection.credential._sd_jwt.encoded,
701
+ });
702
+
703
+ // Verify the presentation is a string (not an array)
704
+ expect(typeof derivedCredential).toBe('string');
705
+
706
+ // Verify it can be decoded
707
+ const decoded = await decodeSDJWT(derivedCredential);
708
+ expect(decoded).toHaveProperty('jwt');
709
+ expect(decoded).toHaveProperty('disclosures');
710
+
711
+ // Verify disclosed attributes
712
+ const disclosedKeys = decoded.disclosures.map((d: any) => d.key);
713
+ expect(disclosedKeys).toContain('name');
714
+ expect(disclosedKeys).toContain('date');
715
+ }
716
+ });
717
+ });
718
+ });